From 17f6bfed0a692f1445761d790c50c01c4d755473 Mon Sep 17 00:00:00 2001 From: ShotaroKataoka Date: Wed, 8 Jul 2026 23:14:10 +0900 Subject: [PATCH] =?UTF-8?q?test(web-ui):=20Playwright=20E2E=20=E2=80=94=20?= =?UTF-8?q?deck=20creation=20via=20chat=20with=20stub=20ACP=20agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roadmap 2-5b-4. One happy-path E2E: deck list → New Deck → send message → workspace navigation → slide preview. Runs the real Local-mode stack (Next.js API routes, ACP process manager, SSE bridge, strandsParser) against a stub agent (e2e/stub-agent.mjs) that speaks minimal ACP JSON-RPC and writes a deck to disk — no AWS, no kiro-cli. - SDPM_ACP_CONFIG_DIR env override in acp-adapter so the test sandbox (e2e/.tmp/) never touches the developer's real acp-config.json - Deck root sandboxed via existing SDPM_DECK_ROOT - CI: new e2e job (chromium only, trace artifact on failure) - npm run test:e2e + README testing sections (en/ja) --- .github/workflows/ci.yml | 26 ++ .gitignore | 3 + web-ui/README.md | 14 + web-ui/README_ja.md | 14 + web-ui/e2e/deck-creation.spec.ts | 45 +++ web-ui/e2e/global-setup.ts | 36 +++ web-ui/e2e/stub-agent.mjs | 96 +++++++ web-ui/package-lock.json | 429 ++++++++++++++++------------ web-ui/package.json | 2 + web-ui/playwright.config.ts | 39 +++ web-ui/src/lib/local/acp-adapter.ts | 4 +- 11 files changed, 525 insertions(+), 183 deletions(-) create mode 100644 web-ui/e2e/deck-creation.spec.ts create mode 100644 web-ui/e2e/global-setup.ts create mode 100644 web-ui/e2e/stub-agent.mjs create mode 100644 web-ui/playwright.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c280054..8a114b45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,32 @@ jobs: run: npm run build:cloud working-directory: web-ui + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: "20" + cache: npm + cache-dependency-path: web-ui/package-lock.json + - name: Install + run: npm ci + working-directory: web-ui + - name: Install Playwright browser + run: npx playwright install --with-deps chromium + working-directory: web-ui + - name: E2E test + run: npm run test:e2e + working-directory: web-ui + - name: Upload trace on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-traces + path: web-ui/test-results/ + retention-days: 7 + infra: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 7c425b43..32e915aa 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ admin-ui/dist/ web-ui/node_modules/ web-ui/.next/ web-ui/public/aws-exports.json +web-ui/e2e/.tmp/ +web-ui/test-results/ +web-ui/playwright-report/ # Kiro / Q-SPEC (local steering, specs, settings) .kiro/* diff --git a/web-ui/README.md b/web-ui/README.md index daa5c94d..6c9468c1 100644 --- a/web-ui/README.md +++ b/web-ui/README.md @@ -152,6 +152,20 @@ export default function AuthenticatedLayout({ children }: { children: React.Reac --- +## Testing + +```bash +npm test # Unit tests (Vitest + Testing Library) +npm run test:e2e # E2E test (Playwright — first run: npx playwright install chromium) +``` + +The E2E test starts the Web UI in Local mode against a stub ACP agent +(`e2e/stub-agent.mjs`), so it needs no AWS and no `kiro-cli`. Decks and agent +config are sandboxed under `e2e/.tmp/` — your real `~/Documents/SDPM-Presentations` +and `mcp-local/.sdpm/` are never touched. + +--- + ## Project Structure ``` diff --git a/web-ui/README_ja.md b/web-ui/README_ja.md index 01f5eadc..263d4a36 100644 --- a/web-ui/README_ja.md +++ b/web-ui/README_ja.md @@ -130,6 +130,20 @@ export default function AuthenticatedLayout({ children }: { children: React.Reac --- +## テスト + +```bash +npm test # ユニットテスト(Vitest + Testing Library) +npm run test:e2e # E2E テスト(Playwright — 初回は npx playwright install chromium) +``` + +E2E テストはスタブ ACP エージェント(`e2e/stub-agent.mjs`)を使って Web UI を +ローカルモードで起動するため、AWS も `kiro-cli` も不要。デッキとエージェント設定は +`e2e/.tmp/` 配下にサンドボックス化され、実際の `~/Documents/SDPM-Presentations` や +`mcp-local/.sdpm/` には触れない。 + +--- + ## プロジェクト構成 ``` diff --git a/web-ui/e2e/deck-creation.spec.ts b/web-ui/e2e/deck-creation.spec.ts new file mode 100644 index 00000000..18a5f5da --- /dev/null +++ b/web-ui/e2e/deck-creation.spec.ts @@ -0,0 +1,45 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +/** + * E2E: deck creation happy path (roadmap 2-5b-4). + * + * Deck list → New Deck → send a chat message → stub agent creates a deck + * and streams tool events → UI navigates to the workspace and shows the + * slide preview. Exercises the full Local-mode stack: React UI, Next.js + * API routes, ACP process manager, SSE bridge, and strandsParser. + */ +import { test, expect } from "@playwright/test" + +const DECK_ID = "e2e-test-deck" + +test("create deck via chat and see slide preview", async ({ page }) => { + await page.goto("/decks") + + // Deck list renders (empty sandbox) + await expect(page.getByRole("heading", { name: "Decks" })).toBeVisible() + + // Open the new-deck chat panel + await page.getByRole("button", { name: "New Deck" }).click() + const input = page.getByRole("textbox", { name: "Chat message input" }) + await expect(input).toBeVisible() + + // Send a message + await input.fill("Create a deck about end-to-end testing") + await page.getByRole("button", { name: "Send message" }).click() + + // User message and streamed assistant reply appear + const chatLog = page.getByRole("log", { name: "Chat messages" }) + await expect(chatLog).toContainText("Create a deck about end-to-end testing") + await expect(chatLog).toContainText("Done! Your deck is ready.", { timeout: 30_000 }) + + // Deck-created tool result navigates to the workspace (hash routing) + await expect(page).toHaveURL(new RegExp(`#${DECK_ID}$`), { timeout: 15_000 }) + + // Workspace polling picks up the slide — the Slides tab appears with a count + const slidesTab = page.getByRole("tab", { name: /Slides/ }) + await expect(slidesTab).toBeVisible({ timeout: 30_000 }) + await slidesTab.click() + + // Slide preview renders from the deck the stub wrote to disk + await expect(page.locator(`img[alt^="Slide 1"]`).first()).toBeVisible({ timeout: 30_000 }) +}) diff --git a/web-ui/e2e/global-setup.ts b/web-ui/e2e/global-setup.ts new file mode 100644 index 00000000..a40d8785 --- /dev/null +++ b/web-ui/e2e/global-setup.ts @@ -0,0 +1,36 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +/** + * Playwright global setup — prepares an isolated sandbox under e2e/.tmp: + * - decks/ : SDPM_DECK_ROOT for the dev server (keeps ~/Documents untouched) + * - config/ : acp-config.json pointing the ACP process manager at the + * stub agent instead of kiro-cli (via SDPM_ACP_CONFIG_DIR) + */ +import fs from "fs" +import path from "path" + +export default async function globalSetup() { + const tmp = path.resolve(__dirname, ".tmp") + fs.rmSync(tmp, { recursive: true, force: true }) + fs.mkdirSync(path.join(tmp, "decks"), { recursive: true }) + fs.mkdirSync(path.join(tmp, "config"), { recursive: true }) + + const stubPath = path.resolve(__dirname, "stub-agent.mjs") + const config = { + activeAgent: "e2e-stub", + agents: [ + { + id: "e2e-stub", + displayName: "E2E Stub Agent", + path: process.execPath, + args: [stubPath], + env: {}, + subagentTool: "use_subagent", + subagentInstruction: "", + restartOnNewChat: true, + subagentQueryField: "query", + }, + ], + } + fs.writeFileSync(path.join(tmp, "config", "acp-config.json"), JSON.stringify(config, null, 2) + "\n") +} diff --git a/web-ui/e2e/stub-agent.mjs b/web-ui/e2e/stub-agent.mjs new file mode 100644 index 00000000..aed19503 --- /dev/null +++ b/web-ui/e2e/stub-agent.mjs @@ -0,0 +1,96 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +/** + * E2E stub ACP agent — stands in for kiro-cli in Playwright tests. + * + * Speaks just enough of the Agent Client Protocol (JSON-RPC over stdio) + * for acp-process.ts: initialize, session/new, session/load, session/prompt. + * On prompt it writes a deck to SDPM_DECK_ROOT (deck.json + slide JSON + + * preview PNG), then emits the same session/update sequence a real agent + * produces: text chunk → tool_call → tool_call_update(completed with deckId) + * → text chunk → end_turn. + */ +import fs from "node:fs" +import path from "node:path" +import readline from "node:readline" + +const DECK_ROOT = process.env.SDPM_DECK_ROOT +const DECK_ID = process.env.SDPM_STUB_DECK_ID || "e2e-test-deck" +const SESSION_ID = "stub-session-1" + +// 1x1 transparent PNG +const PNG_1X1 = Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==", + "base64", +) + +function send(obj) { + process.stdout.write(JSON.stringify(obj) + "\n") +} + +function update(u) { + send({ jsonrpc: "2.0", method: "session/update", params: { sessionId: SESSION_ID, update: u } }) +} + +function createDeckOnDisk() { + const dir = path.join(DECK_ROOT, DECK_ID) + fs.mkdirSync(path.join(dir, "slides"), { recursive: true }) + fs.mkdirSync(path.join(dir, "preview"), { recursive: true }) + fs.writeFileSync(path.join(dir, "deck.json"), JSON.stringify({ name: "E2E Test Deck" })) + fs.writeFileSync( + path.join(dir, "slides", "intro.json"), + JSON.stringify({ slug: "intro", elements: [] }), + ) + fs.writeFileSync(path.join(dir, "preview", "intro.png"), PNG_1X1) + return dir +} + +function handlePrompt(msg) { + update({ sessionUpdate: "agent_message_chunk", content: { type: "text", text: "Creating your deck… " } }) + + const outputDir = createDeckOnDisk() + update({ + sessionUpdate: "tool_call", + toolCallId: "stub-tool-1", + title: "init_presentation", + rawInput: { template: "e2e" }, + }) + update({ + sessionUpdate: "tool_call_update", + toolCallId: "stub-tool-1", + status: "completed", + rawOutput: { + items: [{ Json: { content: [{ text: JSON.stringify({ deckId: DECK_ID, output_dir: outputDir }) }] } }], + }, + }) + + update({ sessionUpdate: "agent_message_chunk", content: { type: "text", text: "Done! Your deck is ready." } }) + send({ jsonrpc: "2.0", id: msg.id, result: { stopReason: "end_turn" } }) +} + +const rl = readline.createInterface({ input: process.stdin }) +rl.on("line", (line) => { + let msg + try { + msg = JSON.parse(line) + } catch { + return + } + switch (msg.method) { + case "initialize": + send({ jsonrpc: "2.0", id: msg.id, result: { protocolVersion: 1, agentCapabilities: {} } }) + break + case "session/new": + send({ jsonrpc: "2.0", id: msg.id, result: { sessionId: SESSION_ID } }) + break + case "session/load": + send({ jsonrpc: "2.0", id: msg.id, result: {} }) + break + case "session/prompt": + handlePrompt(msg) + break + default: + // notifications (session/cancel etc.) — ignore + break + } +}) diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index b86f6661..1af9bf6f 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -38,6 +38,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.61.1", "@shadcn/ui": "^0.0.4", "@tailwindcss/postcss": "^4", "@testing-library/dom": "^10.4.1", @@ -336,13 +337,13 @@ } }, "node_modules/@aws-sdk/client-firehose": { - "version": "3.1080.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-firehose/-/client-firehose-3.1080.0.tgz", - "integrity": "sha512-mxh9xSrHGUs8tzqMzv9RAhbSJgAJdtSC1o61zPwNYNJTvz4NzSdZ4VpA2QNpEptS+mDLWblGmQh2yt619qRY1g==", + "version": "3.1081.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-firehose/-/client-firehose-3.1081.0.tgz", + "integrity": "sha512-194PA2vtfrLH0yPsfOJAcvZA3XiYCpLH1BdTjq34AbFJKo6WG4IvrAJqHyCbQDv7gEQ/7dLhW02te4CA4ld0ZA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/credential-provider-node": "^3.972.63", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/credential-provider-node": "^3.972.64", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", @@ -367,13 +368,13 @@ } }, "node_modules/@aws-sdk/client-kinesis": { - "version": "3.1080.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-kinesis/-/client-kinesis-3.1080.0.tgz", - "integrity": "sha512-jHQ6egJUweSKcJFWUK4HYkLZPImDqyGv+LXfAQcBcRlArN7n7IzP7sSQVCyUCGscQvZ6Pl1FYPAkyN6xCHxfbg==", + "version": "3.1081.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-kinesis/-/client-kinesis-3.1081.0.tgz", + "integrity": "sha512-9ZPt3l/FtRByUY21JThrmxyQa5iOJ8X732AX2kly88vUtnouC7RBF8nAJoabLwdMG9E3x1H6Izvdi7Wcc16TBg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/credential-provider-node": "^3.972.63", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/credential-provider-node": "^3.972.64", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", @@ -398,13 +399,13 @@ } }, "node_modules/@aws-sdk/client-personalize-events": { - "version": "3.1080.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-personalize-events/-/client-personalize-events-3.1080.0.tgz", - "integrity": "sha512-JqkcHzGhL7unTkaDt9Ac7n9PbGfsx51VY9eKB+8TVKDOu4+CkCsFYtTonFhR7n3zAmMQA2YVGenrtDzYiMm0cg==", + "version": "3.1081.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-personalize-events/-/client-personalize-events-3.1081.0.tgz", + "integrity": "sha512-9AGbZUZ6nwfCh/Z8P7vljHQ9J0asINnP3Zqms0GfZfivaWOiaSDKhAklXCenh/D1+cnCTZJMIczW+FqME/qPzA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/credential-provider-node": "^3.972.63", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/credential-provider-node": "^3.972.64", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", @@ -429,9 +430,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.974.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.28.tgz", - "integrity": "sha512-4/1DtLwgLqzIg2uFzkFaFjMQHhhhwHIZN4PfziIVqXYX7koO78omuchQlLHyzQBw80l255dtmTA/J4W5yhb7zw==", + "version": "3.974.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.29.tgz", + "integrity": "sha512-yqKcltLbtRh1ubzhRSldIs8jFHNZlyMlgoIccCC0aDVbrB99nXaBdmfr89mK7obWX/NVg4rAMpCpZ6dCDiVBtA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.15", @@ -460,12 +461,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.54", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.54.tgz", - "integrity": "sha512-F4WQCG8GULIt+XrMHsqUM9dZc0eTwZM3HUWByOjIKOwBqJSzUxX8CFAtbgMvWfCua52FS1oi5FXarH3+khFq8A==", + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.55.tgz", + "integrity": "sha512-Ah36tYkqyaVnaHkx7VseoTYrHUmwgBps3V+wnrC1idhIIMGlviH0FtrX9EIPdAlVHvXC7FQZLhmHBRz+pLaiWg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", + "@aws-sdk/core": "^3.974.29", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", @@ -488,12 +489,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.56", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.56.tgz", - "integrity": "sha512-PiwHgEK2srdfi/lFveyQ+3w/qikyh6MKvuZAdlMC+dwQi4K5iVBr32oh7cugnYw+jA8iGtnQMhCGvLm/mqplmw==", + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.57.tgz", + "integrity": "sha512-/vp6i5YEliJqRm5k/BDmYjAyRAMTdkjW6UciVRk9oh/0OfDCWeb/ih7hqte4lFvKXkIbsqe9AdK9LQK6NGardw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", + "@aws-sdk/core": "^3.974.29", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", @@ -518,19 +519,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.61", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.61.tgz", - "integrity": "sha512-9CZWMjhBlgfKlqJk40R7kvMOLEIoOr3HJ1J/ELjAH9H5LzR4bCxLujPVM/1PKAEjzSZKZCxWOalm7JUGZbhQqw==", + "version": "3.972.62", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.62.tgz", + "integrity": "sha512-pQIRiQQs+MUlVnJdWJ7/6KS0WxcLRVfut57OFgwC3cnM1F8mXw3Kh4gAVwj6AtvD6CWx8x6+po4ENRcqe64XrQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/credential-provider-env": "^3.972.54", - "@aws-sdk/credential-provider-http": "^3.972.56", - "@aws-sdk/credential-provider-login": "^3.972.60", - "@aws-sdk/credential-provider-process": "^3.972.54", - "@aws-sdk/credential-provider-sso": "^3.972.60", - "@aws-sdk/credential-provider-web-identity": "^3.972.60", - "@aws-sdk/nested-clients": "^3.997.28", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/credential-provider-env": "^3.972.55", + "@aws-sdk/credential-provider-http": "^3.972.57", + "@aws-sdk/credential-provider-login": "^3.972.61", + "@aws-sdk/credential-provider-process": "^3.972.55", + "@aws-sdk/credential-provider-sso": "^3.972.61", + "@aws-sdk/credential-provider-web-identity": "^3.972.61", + "@aws-sdk/nested-clients": "^3.997.29", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", @@ -554,13 +555,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.60", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.60.tgz", - "integrity": "sha512-Ak6OOrCbXvACyxLFIP1mcS+JTLS9ZpW1ZqyBtqu6axvdpsbG1gVNhUlAfQq8TK/gar/h2w35LrzlQU0PcUzJpw==", + "version": "3.972.61", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.61.tgz", + "integrity": "sha512-jtrxWwC7slqxh7DnAWHrwsA3UwCsnlypdYtavGT7EX5p791wxWQys7QzkCZ7JvOMAyylDtPoxyV+ic0zg3rV9g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/nested-clients": "^3.997.28", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/nested-clients": "^3.997.29", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", @@ -583,17 +584,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.63", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.63.tgz", - "integrity": "sha512-YmgWtTPZDStyT74ApSHpApD3r7W9znsc+WEZjW0vceom+NAxRx9/F3TyukOKix8kJkPaa49aIREQJdpGeLDiEw==", + "version": "3.972.64", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.64.tgz", + "integrity": "sha512-zyKVYDyMR9VQL/kPi03ygN2vtD9uLMuWRLoJ77KxgZZaS1VlJloI+SzleF9Zg4HWUI+AIu+ZRs8zsJFNqbrxsw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.54", - "@aws-sdk/credential-provider-http": "^3.972.56", - "@aws-sdk/credential-provider-ini": "^3.972.61", - "@aws-sdk/credential-provider-process": "^3.972.54", - "@aws-sdk/credential-provider-sso": "^3.972.60", - "@aws-sdk/credential-provider-web-identity": "^3.972.60", + "@aws-sdk/credential-provider-env": "^3.972.55", + "@aws-sdk/credential-provider-http": "^3.972.57", + "@aws-sdk/credential-provider-ini": "^3.972.62", + "@aws-sdk/credential-provider-process": "^3.972.55", + "@aws-sdk/credential-provider-sso": "^3.972.61", + "@aws-sdk/credential-provider-web-identity": "^3.972.61", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/credential-provider-imds": "^4.4.5", @@ -617,12 +618,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.54", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.54.tgz", - "integrity": "sha512-UNmUjtTnp3wH3YTZcctN7yK17S2AdaJpiNIdIf0l70hHOdGuDfdMxk62P5rt64GwGm6qkFOYG0js/cU6cdZDdg==", + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.55.tgz", + "integrity": "sha512-x0XjjF0l1WGRtK2vEhTZqCguQuAIZLep9l2+eeEmuxQQjjD3BlGQXY5xADR+l3t576UX+dxRkRtTjEu40l81Vw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", + "@aws-sdk/core": "^3.974.29", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", @@ -645,14 +646,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.60", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.60.tgz", - "integrity": "sha512-zH8SvJkTRw1Kb7GARjGPjzkQPfL3jDi/OxK32I5SQq7bgwgFHJZaoDEwkEvFlfNfpByM6n4Rs20Y1mLyOdb6ag==", + "version": "3.972.61", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.61.tgz", + "integrity": "sha512-d/V0VRsz73i+PHhbult/tx0Y1+de1SNQVsXkcQCmpfeBq7uODy/RTxNsOLpT9ZVHxcRNzbQFuywLKC33fUMIxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/nested-clients": "^3.997.28", - "@aws-sdk/token-providers": "3.1080.0", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/nested-clients": "^3.997.29", + "@aws-sdk/token-providers": "3.1081.0", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", @@ -675,13 +676,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.60", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.60.tgz", - "integrity": "sha512-q2rJSQ/AMjemUS18OtQqczqSo2R6VjOCLmLVmLVcdrP5/AKoj632lGCrMUWQ6EgnaLMEHbq0UO4mWh/u6gjPhA==", + "version": "3.972.61", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.61.tgz", + "integrity": "sha512-Bv4n3NOI6hPy+rmr6Bw9R6LnBVRkcp3ncj2E2IKSYJG+0UkysSitWMvbgndNvMxDw7gE1pQ/ErwkNceuKwj7zQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/nested-clients": "^3.997.28", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/nested-clients": "^3.997.29", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", @@ -704,12 +705,12 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.28.tgz", - "integrity": "sha512-1oG/mM3jmE2M3kad2zWJS6IKIY8hjRV4l5kAgg+xTQdLMTtehhcSucL/y4WqQpcHmQwi6+gRK8E46GYl1NBW9Q==", + "version": "3.997.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.29.tgz", + "integrity": "sha512-ot6v8J5W8P0w6ryyuIkXP1bHZHTlvwtn83mVCYaBE0GJ6tJX4vPSBx7M98w9O4wmmDruFsDBUMjhEHA+OosUFQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", + "@aws-sdk/core": "^3.974.29", "@aws-sdk/signature-v4-multi-region": "^3.996.38", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", @@ -762,13 +763,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1080.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1080.0.tgz", - "integrity": "sha512-8PufAQvncWXvdZUvODbuyXa8l3aszefEzwSMBUcgheNbZOmJMcNn388Ebt/piVrUHyxKN1MlxnR2OonzTyZaGw==", + "version": "3.1081.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1081.0.tgz", + "integrity": "sha512-kduAeI6cL+zqwj3gjPh9LhuX7kBZ83msYxutavaR+UPm5K8J7iThJBvNRAsFNyWTji92CSU8dogUgvi9T0BehA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.28", - "@aws-sdk/nested-clients": "^3.997.28", + "@aws-sdk/core": "^3.974.29", + "@aws-sdk/nested-clients": "^3.997.29", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/types": "^4.15.1", @@ -2986,15 +2987,32 @@ "license": "MIT" }, "node_modules/@oxc-project/types": { - "version": "0.138.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", - "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", @@ -4491,9 +4509,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", - "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -4508,9 +4526,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", - "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -4525,9 +4543,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", - "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -4542,9 +4560,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", - "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -4559,9 +4577,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", - "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -4576,9 +4594,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", - "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -4593,9 +4611,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", - "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], @@ -4610,9 +4628,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", - "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -4627,9 +4645,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", - "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -4644,9 +4662,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", - "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -4661,9 +4679,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", - "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -4678,9 +4696,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", - "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -4695,9 +4713,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", - "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], @@ -4748,9 +4766,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", - "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -4765,9 +4783,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", - "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -5643,9 +5661,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.4.tgz", - "integrity": "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==", + "version": "25.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", + "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", "dev": true, "license": "MIT", "peer": true, @@ -7110,9 +7128,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -7266,9 +7284,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001802", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001802.tgz", - "integrity": "sha512-vmv8ub2xwTNmljSKf82mtCk5JH7hC+YgzLj3P5zotvA0tPQ9016tdNNOG8WRca1IxOnhSsivB+J0z5FeE5LOUw==", + "version": "1.0.30001803", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz", + "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==", "funding": [ { "type": "opencollective", @@ -8211,9 +8229,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.388", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.388.tgz", - "integrity": "sha512-Pl/aJaqOOxYxda3vcx1IKSJimwYXHDkEnGn0F+kG2EE68dDtx2uCinaS+Vih8Z91B9t8CSAbiF/HKyWcnXjhzw==", + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", "dev": true, "license": "ISC" }, @@ -9525,9 +9543,9 @@ } }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -11033,9 +11051,9 @@ } }, "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -12620,9 +12638,9 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.14.6", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.14.6.tgz", - "integrity": "sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.15.0.tgz", + "integrity": "sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13384,9 +13402,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.1.tgz", - "integrity": "sha512-h7bxdzhHk8Knyc4Tj+jMaa7fEEoUJy7p1qtbVgkYg1Uhpe5Np5VuGXCRZnkZvU+Q42M1vStt0ifa3ueykRJPmQ==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", "funding": [ { "type": "github", @@ -13537,6 +13555,38 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -14287,13 +14337,13 @@ } }, "node_modules/rolldown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", - "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.138.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -14303,21 +14353,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.4", - "@rolldown/binding-darwin-arm64": "1.1.4", - "@rolldown/binding-darwin-x64": "1.1.4", - "@rolldown/binding-freebsd-x64": "1.1.4", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", - "@rolldown/binding-linux-arm64-gnu": "1.1.4", - "@rolldown/binding-linux-arm64-musl": "1.1.4", - "@rolldown/binding-linux-ppc64-gnu": "1.1.4", - "@rolldown/binding-linux-s390x-gnu": "1.1.4", - "@rolldown/binding-linux-x64-gnu": "1.1.4", - "@rolldown/binding-linux-x64-musl": "1.1.4", - "@rolldown/binding-openharmony-arm64": "1.1.4", - "@rolldown/binding-wasm32-wasi": "1.1.4", - "@rolldown/binding-win32-arm64-msvc": "1.1.4", - "@rolldown/binding-win32-x64-msvc": "1.1.4" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/router": { @@ -15288,9 +15338,9 @@ } }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, @@ -15690,9 +15740,9 @@ "license": "MIT" }, "node_modules/systeminformation": { - "version": "5.31.13", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.13.tgz", - "integrity": "sha512-iUJXJoKzm4vtLSeT3nwe2s9QjoJAxHg7wYJ0KaQ54Xy2u9jsTq0ULWQQ0+T72FXjX2XnGqubazNx9lUfng7ELw==", + "version": "5.31.15", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.15.tgz", + "integrity": "sha512-7mqCtD28TK5dVdLAQONVa/Do/NBgMH2dxqf49nh6DIKoEWuDg6tkgGBP+dN22VEJVPZa/QqiHomhWNRn4WUNTQ==", "dev": true, "license": "MIT", "os": [ @@ -15844,22 +15894,22 @@ } }, "node_modules/tldts": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.6.tgz", - "integrity": "sha512-rbP0Gyx8b3Ae9yO//CU2wbSnQNoQ66m1nJdSbSHmnwKwzkkz/u8mERYU8T2rmlmy+bJvRNn84yNCW8gYqox44Q==", + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.7.tgz", + "integrity": "sha512-56L0/9HELHSsG1bFCzay8UoLxzRL7kpFf7Wl5q/kSYwiSJGACvro61xnKzPNM+SadxllzdtXsKDSXE7HPeqIAw==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.4.6" + "tldts-core": "^7.4.7" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.6.tgz", - "integrity": "sha512-TkQNGJIhlEphpHCjKodMTSe23egUZr/g+flI2qkLgiJ/maAzSgXypSLRTNH3nCmqgayEmtcJBiLcfODSAr1xoA==", + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.7.tgz", + "integrity": "sha512-rNlAI8fKn/JckBMUSbNL/ES2kmDiurWaE49l+ikwEc9A6lFR7gMx9AhgQMQKBK4H5w4pKLH64JzZfB99uRsGNQ==", "dev": true, "license": "MIT" }, @@ -15887,9 +15937,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -16619,6 +16669,21 @@ } } }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", diff --git a/web-ui/package.json b/web-ui/package.json index 393e542e..602e4729 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -13,6 +13,7 @@ "lint:fix": "eslint src/ --fix", "test": "vitest run", "test:watch": "vitest", + "test:e2e": "playwright test", "clean": "rm -rf build/ node_modules/ .next/" }, "dependencies": { @@ -45,6 +46,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.61.1", "@shadcn/ui": "^0.0.4", "@tailwindcss/postcss": "^4", "@testing-library/dom": "^10.4.1", diff --git a/web-ui/playwright.config.ts b/web-ui/playwright.config.ts new file mode 100644 index 00000000..4524eacc --- /dev/null +++ b/web-ui/playwright.config.ts @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +/** + * Playwright E2E config — runs the Web UI in Local mode against a stub + * ACP agent (e2e/stub-agent.mjs), so no AWS and no kiro-cli are needed. + * + * Sandbox: decks and agent config live under e2e/.tmp (see global-setup.ts). + */ +import { defineConfig } from "@playwright/test" +import path from "path" + +const TMP = path.resolve(__dirname, "e2e", ".tmp") +const PORT = 3199 + +export default defineConfig({ + testDir: "./e2e", + testMatch: "**/*.spec.ts", + globalSetup: "./e2e/global-setup.ts", + timeout: 60_000, + // Single worker: tests share one dev server and one deck sandbox + workers: 1, + retries: process.env.CI ? 1 : 0, + reporter: process.env.CI ? "github" : "list", + use: { + baseURL: `http://localhost:${PORT}`, + trace: "retain-on-failure", + }, + webServer: { + command: `npx next dev --turbopack --port ${PORT}`, + port: PORT, + reuseExistingServer: false, + timeout: 120_000, + env: { + NEXT_PUBLIC_MODE: "local", + SDPM_DECK_ROOT: path.join(TMP, "decks"), + SDPM_ACP_CONFIG_DIR: path.join(TMP, "config"), + }, + }, +}) diff --git a/web-ui/src/lib/local/acp-adapter.ts b/web-ui/src/lib/local/acp-adapter.ts index 6cb6761a..a253a3fe 100644 --- a/web-ui/src/lib/local/acp-adapter.ts +++ b/web-ui/src/lib/local/acp-adapter.ts @@ -36,7 +36,9 @@ const PRESETS: AgentConfig[] = [ ] const MCP_LOCAL_DIR = path.resolve(process.cwd(), "..", "mcp-local") -const CONFIG_DIR = path.join(MCP_LOCAL_DIR, ".sdpm") +// SDPM_ACP_CONFIG_DIR: override for tests (E2E points this at a sandbox +// so the stub agent config never touches the developer's real one) +const CONFIG_DIR = process.env.SDPM_ACP_CONFIG_DIR || path.join(MCP_LOCAL_DIR, ".sdpm") const CONFIG_PATH = path.join(CONFIG_DIR, "acp-config.json") interface StoredConfig {