diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 88a4e5c1aeca..3517881c4828 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,14 +1,28 @@ name: E2E Tests + on: workflow_call: + secrets: + AZURE_CLIENT_ID: + required: false + AZURE_TENANT_ID: + required: false + AZURE_SUBSCRIPTION_ID: + required: false + PLAYWRIGHT_SERVICE_URL: + required: false + # Add other secrets if necessary workflow_dispatch: +env: + TELEMETRY_DISABLED: 1 + permissions: + id-token: write contents: read actions: read - checks: write -env: - TELEMETRY_DISABLED: 1 + checks: write + jobs: build: name: Run E2E Tests @@ -88,7 +102,31 @@ jobs: - name: Install Playwright run: pnpm exec playwright install --with-deps - - name: Run E2E Tests + - name: Set Azure Secret Variables + run: | + if [[ -n "${{ secrets.AZURE_CLIENT_ID }}" && -n "${{ secrets.AZURE_TENANT_ID }}" && -n "${{ secrets.AZURE_SUBSCRIPTION_ID }}" ]]; then + echo "AZURE_ENABLED=true" >> $GITHUB_ENV + else + echo "AZURE_ENABLED=false" >> $GITHUB_ENV + fi + + - name: Azure login + if: env.AZURE_ENABLED == 'true' + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Run E2E Tests (Azure) + if: env.AZURE_ENABLED == 'true' + env: + PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }} + run: | + pnpm test-e2e:azure + + - name: Run E2E Tests (Local) + if: env.AZURE_ENABLED == 'false' run: | pnpm test:e2e diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 9ce7eca74f6b..ae703a4e4011 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,6 +6,7 @@ permissions: pull-requests: read actions: read checks: write + id-token: write on: pull_request: @@ -19,53 +20,28 @@ concurrency: cancel-in-progress: true jobs: - changes: - name: Detect changes - runs-on: ubuntu-latest - outputs: - has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }} - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/dangerous-git-checkout - - uses: dorny/paths-filter@v2 - id: filter - with: - filters: | - has-files-requiring-all-checks: - - "!(**.md|.github/CODEOWNERS)" - test: name: Run Unit Tests - needs: [changes] - if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/test.yml secrets: inherit lint: name: Run Linters - needs: [changes] - if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/lint.yml secrets: inherit build: name: Build Formbricks-web - needs: [changes] - if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/build-web.yml secrets: inherit docs: name: Build Docs - needs: [changes] - if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/build-docs.yml secrets: inherit e2e-test: name: Run E2E Tests - needs: [changes] - if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/e2e.yml secrets: inherit diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx index 3788beba3669..fe6f46d92f49 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx @@ -166,7 +166,7 @@ export const MatrixQuestionForm = ({
handleKeyDown(e, "row")} - key={`row-${index}-${question.rows.length}`}> + key={`row-${index}`}> handleKeyDown(e, "column")} - key={`column-${index}-${question.columns.length}`}> + key={`column-${index}`}> + + + + +

This is my sample page using the Formbricks JS javascript widget

+ +`; test.describe("JS Package Test", async () => { + let server: http.Server; let environmentId: string; - test("Tests", async ({ page, users }) => { - await test.step("Admin creates an In-App Survey", async () => { - const user = await users.create(); - await user.login(); - - await page.waitForURL(/\/environments\/[^/]+\/surveys/); - - await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).isVisible(); - await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).click(); - - await page.getByRole("button", { name: "Use this template" }).isVisible(); - await page.getByRole("button", { name: "Use this template" }).click(); - - await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/edit/); - - await page.getByRole("button", { name: "Settings", exact: true }).click(); - - await expect(page.locator("#howToSendCardTrigger")).toBeVisible(); - await page.locator("#howToSendCardTrigger").click(); - - await expect(page.locator("#howToSendCardOption-app")).toBeVisible(); - await page.locator("#howToSendCardOption-app").click(); + test.beforeAll(async () => { + // Create a simple HTTP server + server = http.createServer((_, res) => { + const htmlContent = HTML_TEMPLATE.replace("ENVIRONMENT_ID", environmentId || ""); + res.writeHead(200, { "Content-Type": "text/html" }); + res.end(htmlContent); + }); - await page.locator("#whenToSendCardTrigger").click(); + await new Promise((resolve) => { + server.listen(3004, () => resolve()); + }); + }); - await page.getByRole("button", { name: "Add action" }).click(); - await page.getByText("New SessionGets fired when a").click(); + test.afterAll(async () => { + // Cleanup: close the server + await new Promise((resolve) => server.close(resolve)); + }); - await page.locator("#recontactOptionsCardTrigger").click(); + test("Create, display and validate PMF survey", async ({ page, users }) => { + // Create and login user + const user = await users.create(); + await user.login(); - await page.locator("label").filter({ hasText: "Keep showing while conditions" }).click(); - await page.locator("#recontactDays").check(); + await page.waitForURL(/\/environments\/[^/]+\/surveys/); - await page.getByRole("button", { name: "Publish" }).click(); + // Extract environmentId early in the test + environmentId = + /\/environments\/([^/]+)\/surveys/.exec(page.url())?.[1] ?? + (() => { + throw new Error("Unable to parse environmentId from URL"); + })(); - environmentId = - /\/environments\/([^/]+)\/surveys/.exec(page.url())?.[1] ?? - (() => { - throw new Error("Unable to parse environmentId from URL"); - })(); + // Create survey from template + await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).isVisible(); + await page.getByRole("heading", { name: "Product Market Fit (Superhuman)" }).click(); + await page.getByRole("button", { name: "Use this template" }).isVisible(); + await page.getByRole("button", { name: "Use this template" }).click(); - await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/summary/); - }); + // Configure survey settings + await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/edit/); + await page.getByRole("button", { name: "Settings", exact: true }).click(); - await test.step("JS display survey on page and submit response", async () => { - let currentDir = process.cwd(); - let htmlFilePath = currentDir + "/packages/js/index.html"; - - let htmlFile = replaceEnvironmentIdInHtml(htmlFilePath, environmentId); - await page.goto(htmlFile); - - // Formbricks In App Sync has happened - const syncApi = await page.waitForResponse( - (response) => { - return response.url().includes("/environment"); - }, - { - timeout: 120000, - } - ); - - expect(syncApi.status()).toBe(200); - - // Formbricks Modal exists in the DOM - await expect(page.locator("#formbricks-modal-container")).toHaveCount(1); - - // Formbricks Modal is visible - await expect( - page.locator("#questionCard-0").getByRole("link", { name: "Powered by Formbricks" }) - ).toBeVisible(); - - // Fill the Survey - await page.getByRole("button", { name: "Happy to help!" }).click(); - await page.locator("label").filter({ hasText: "Somewhat disappointed" }).click(); - await page.locator("#questionCard-1").getByRole("button", { name: "Next" }).click(); - await page.locator("label").filter({ hasText: "Founder" }).click(); - await page.locator("#questionCard-2").getByRole("button", { name: "Next" }).click(); - await page - .locator("#questionCard-3") - .getByLabel("textarea") - .fill("People who believe that PMF is necessary"); - await page.locator("#questionCard-3").getByRole("button", { name: "Next" }).click(); - await page.locator("#questionCard-4").getByLabel("textarea").fill("Much higher response rates!"); - await page.locator("#questionCard-4").getByRole("button", { name: "Next" }).click(); - await page.locator("#questionCard-5").getByLabel("textarea").fill("Make this end to end test pass!"); - await page.getByRole("button", { name: "Finish" }).click(); - - // loading spinner -> wait for it to disappear - await page.getByTestId("loading-spinner").waitFor({ state: "hidden" }); - await page.waitForLoadState("networkidle"); - }); + await expect(page.locator("#howToSendCardTrigger")).toBeVisible(); + await page.locator("#howToSendCardTrigger").click(); + await expect(page.locator("#howToSendCardOption-app")).toBeVisible(); + await page.locator("#howToSendCardOption-app").click(); - await test.step("Admin validates Displays & Response", async () => { - await page.goto("/"); - await page.waitForURL(/\/environments\/[^/]+\/surveys/); + await page.locator("#whenToSendCardTrigger").click(); + await page.getByRole("button", { name: "Add action" }).click(); + await page.getByText("New SessionGets fired when a").click(); - await page.getByRole("link", { name: "product Market Fit (Superhuman)" }).click(); - (await page.waitForSelector("text=Responses")).isVisible(); + await page.locator("#recontactOptionsCardTrigger").click(); + await page.locator("label").filter({ hasText: "Keep showing while conditions" }).click(); + await page.locator("#recontactDays").check(); - await page.waitForLoadState("networkidle"); - await page.waitForTimeout(2000); + await page.getByRole("button", { name: "Publish" }).click(); - const impressionsCount = await page.getByRole("button", { name: "Impressions" }).innerText(); - expect(impressionsCount).toEqual("Impressions\n\n1"); + await page.waitForURL(/\/environments\/[^/]+\/surveys\/[^/]+\/summary/); - await expect(page.getByRole("link", { name: "Responses (1)" })).toBeVisible(); - await expect(page.getByRole("button", { name: "Completed 100%" })).toBeVisible(); + // No need for file operations anymore, just use the server + await page.goto("http://localhost:3004"); - await expect(page.getByText("1 Responses", { exact: true }).first()).toBeVisible(); - await expect(page.getByText("CTR100%")).toBeVisible(); - await expect(page.getByText("Somewhat disappointed100%")).toBeVisible(); - await expect(page.getByText("Founder100%")).toBeVisible(); - await expect(page.getByText("People who believe that PMF").first()).toBeVisible(); - await expect(page.getByText("Much higher response rates!").first()).toBeVisible(); - await expect(page.getByText("Make this end to end test").first()).toBeVisible(); + const syncApi = await page.waitForResponse((response) => response.url().includes("/environment"), { + timeout: 120000, }); + expect(syncApi.status()).toBe(200); + + await expect(page.locator("#formbricks-modal-container")).toHaveCount(1); + await expect( + page.locator("#questionCard-0").getByRole("link", { name: "Powered by Formbricks" }) + ).toBeVisible(); + + // Fill the survey + await page.getByRole("button", { name: "Happy to help!" }).click(); + await page.locator("label").filter({ hasText: "Somewhat disappointed" }).click(); + await page.locator("#questionCard-1").getByRole("button", { name: "Next" }).click(); + await page.locator("label").filter({ hasText: "Founder" }).click(); + await page.locator("#questionCard-2").getByRole("button", { name: "Next" }).click(); + await page + .locator("#questionCard-3") + .getByLabel("textarea") + .fill("People who believe that PMF is necessary"); + await page.locator("#questionCard-3").getByRole("button", { name: "Next" }).click(); + await page.locator("#questionCard-4").getByLabel("textarea").fill("Much higher response rates!"); + await page.locator("#questionCard-4").getByRole("button", { name: "Next" }).click(); + await page.locator("#questionCard-5").getByLabel("textarea").fill("Make this end to end test pass!"); + await page.getByRole("button", { name: "Finish" }).click(); + + await page.getByTestId("loading-spinner").waitFor({ state: "hidden" }); + await page.waitForLoadState("networkidle"); + + // Validate displays and response + await page.goto("/"); + await page.waitForURL(/\/environments\/[^/]+\/surveys/); + await page.getByRole("link", { name: "product Market Fit (Superhuman)" }).click(); + await page.waitForSelector("text=Responses"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(2000); + + const impressionsCount = await page.getByRole("button", { name: "Impressions" }).innerText(); + expect(impressionsCount).toEqual("Impressions\n\n1"); + + await expect(page.getByRole("link", { name: "Responses (1)" })).toBeVisible(); + await expect(page.getByRole("button", { name: "Completed 100%" })).toBeVisible(); + await expect(page.getByText("1 Responses", { exact: true }).first()).toBeVisible(); + await expect(page.getByText("CTR100%")).toBeVisible(); + await expect(page.getByText("Somewhat disappointed100%")).toBeVisible(); + await expect(page.getByText("Founder100%")).toBeVisible(); + await expect(page.getByText("People who believe that PMF").first()).toBeVisible(); + await expect(page.getByText("Much higher response rates!").first()).toBeVisible(); + await expect(page.getByText("Make this end to end test").first()).toBeVisible(); }); }); diff --git a/apps/web/playwright/survey.spec.ts b/apps/web/playwright/survey.spec.ts index 8e2487242ff1..9529c9289bf7 100644 --- a/apps/web/playwright/survey.spec.ts +++ b/apps/web/playwright/survey.spec.ts @@ -12,8 +12,6 @@ test.use({ test.describe("Survey Create & Submit Response without logic", async () => { let url: string | null; - test.slow(); - test("Create survey and submit response", async ({ page, users }) => { const user = await users.create(); await user.login(); diff --git a/package.json b/package.json index 8908063a9b80..ef02f4683eb2 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,13 @@ "release": "turbo run build --filter=@formbricks/js... && changeset publish", "test": "turbo run test --no-cache", "test:e2e": "playwright test", + "test-e2e:azure": "pnpm test:e2e -c playwright.service.config.ts --workers=20", "prepare": "husky install", "storybook": "turbo run storybook", "fb-migrate-dev": "pnpm --filter @formbricks/database create-migration && pnpm prisma generate" }, "devDependencies": { + "@azure/microsoft-playwright-testing": "1.0.0-beta.6", "@formbricks/eslint-config": "workspace:*", "@playwright/test": "1.49.1", "eslint": "8.57.0", diff --git a/packages/js/index.html b/packages/js/index.html deleted file mode 100644 index 542f990bbce1..000000000000 --- a/packages/js/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - -

This is my sample page using the Formbricks JS javascript widget

- diff --git a/playwright.service.config.ts b/playwright.service.config.ts new file mode 100644 index 000000000000..1b73f41fd8e1 --- /dev/null +++ b/playwright.service.config.ts @@ -0,0 +1,22 @@ +import { ServiceOS, getServiceConfig } from "@azure/microsoft-playwright-testing"; +import { defineConfig } from "@playwright/test"; +import config from "./playwright.config"; + +/* Learn more about service configuration at https://aka.ms/mpt/config */ +export default defineConfig( + config, + getServiceConfig(config, { + exposeNetwork: "", + timeout: 30000, + os: ServiceOS.LINUX, + useCloudHostedBrowsers: true, // Set to false if you want to only use reporting and not cloud hosted browsers + }), + { + /* + Playwright Testing service reporter is added by default. + This will override any reporter options specified in the base playwright config. + If you are using more reporters, please update your configuration accordingly. + */ + reporter: [["list"], ["@azure/microsoft-playwright-testing/reporter"]], + } +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 186336654ef1..916ab22514db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,9 @@ importers: specifier: 2.27.10 version: 2.27.10 devDependencies: + '@azure/microsoft-playwright-testing': + specifier: 1.0.0-beta.6 + version: 1.0.0-beta.6(@playwright/test@1.49.1) '@formbricks/eslint-config': specifier: workspace:* version: link:packages/config-eslint @@ -1454,6 +1457,76 @@ packages: resolution: {integrity: sha512-2GPCwlNxeHspoK/Mc8nbk9cBOkSpp3j2SJUQmFnyQK6V/pR6II2oPRyZkMomug1Rc10hqlBHByMecq4zhV2uUw==} engines: {node: '>=16.0.0'} + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.9.0': + resolution: {integrity: sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==} + engines: {node: '>=18.0.0'} + + '@azure/core-client@1.9.2': + resolution: {integrity: sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==} + engines: {node: '>=18.0.0'} + + '@azure/core-http-compat@2.1.2': + resolution: {integrity: sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ==} + engines: {node: '>=18.0.0'} + + '@azure/core-lro@2.7.2': + resolution: {integrity: sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==} + engines: {node: '>=18.0.0'} + + '@azure/core-paging@1.6.2': + resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.18.2': + resolution: {integrity: sha512-IkTf/DWKyCklEtN/WYW3lqEsIaUDshlzWRlZNNwSYtFcCBQz++OtOjxNpm8rr1VcbMS6RpjybQa3u6B6nG0zNw==} + engines: {node: '>=18.0.0'} + + '@azure/core-tracing@1.2.0': + resolution: {integrity: sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==} + engines: {node: '>=18.0.0'} + + '@azure/core-util@1.11.0': + resolution: {integrity: sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==} + engines: {node: '>=18.0.0'} + + '@azure/core-xml@1.4.4': + resolution: {integrity: sha512-J4FYAqakGXcbfeZjwjMzjNcpcH4E+JtEBv+xcV1yL0Ydn/6wbQfeFKTCHh9wttAi0lmajHw7yBbHPRG+YHckZQ==} + engines: {node: '>=18.0.0'} + + '@azure/identity@4.5.0': + resolution: {integrity: sha512-EknvVmtBuSIic47xkOqyNabAme0RYTw52BTMz8eBgU1ysTyMrD1uOoM+JdS0J/4Yfp98IBT3osqq3BfwSaNaGQ==} + engines: {node: '>=18.0.0'} + + '@azure/logger@1.1.4': + resolution: {integrity: sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ==} + engines: {node: '>=18.0.0'} + + '@azure/microsoft-playwright-testing@1.0.0-beta.6': + resolution: {integrity: sha512-TVfBM5AL1t88VGNRfj6cudEYURVc4Y/n0tnL+o+TDL9OpAvGa8fZ2xPjXy6imjnX9dgpNjbowMG4pxCKo00OvA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@playwright/test': ^1.43.1 + + '@azure/msal-browser@3.28.0': + resolution: {integrity: sha512-1c1qUF6vB52mWlyoMem4xR1gdwiQWYEQB2uhDkbAL4wVJr8WmAcXybc1Qs33y19N4BdPI8/DHI7rPE8L5jMtWw==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@14.16.0': + resolution: {integrity: sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@2.16.2': + resolution: {integrity: sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==} + engines: {node: '>=16'} + + '@azure/storage-blob@12.26.0': + resolution: {integrity: sha512-SriLPKezypIsiZ+TtlFfE46uuBIap2HeaQVS78e1P7rz5OSbq0rsd52WE1mC5f7vAeLiXqv7I7oRhL3WFZEw3Q==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.10.4': resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} @@ -12341,6 +12414,10 @@ packages: stickyfill@1.1.1: resolution: {integrity: sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==} + stoppable@1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + storybook@8.4.7: resolution: {integrity: sha512-RP/nMJxiWyFc8EVMH5gp20ID032Wvk+Yr3lmKidoegto5Iy+2dVQnUoElZb2zpbVXNHWakGuAkfI0dY1Hfp/vw==} hasBin: true @@ -14401,6 +14478,138 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-auth@1.9.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.11.0 + tslib: 2.8.1 + + '@azure/core-client@1.9.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-rest-pipeline': 1.18.2 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-http-compat@2.1.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-client': 1.9.2 + '@azure/core-rest-pipeline': 1.18.2 + transitivePeerDependencies: + - supports-color + + '@azure/core-lro@2.7.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + + '@azure/core-paging@1.6.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-rest-pipeline@1.18.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-tracing@1.2.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.11.0': + dependencies: + '@azure/abort-controller': 2.1.2 + tslib: 2.8.1 + + '@azure/core-xml@1.4.4': + dependencies: + fast-xml-parser: 4.5.1 + tslib: 2.8.1 + + '@azure/identity@4.5.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-client': 1.9.2 + '@azure/core-rest-pipeline': 1.18.2 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + '@azure/msal-browser': 3.28.0 + '@azure/msal-node': 2.16.2 + events: 3.3.0 + jws: 4.0.0 + open: 8.4.2 + stoppable: 1.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/logger@1.1.4': + dependencies: + tslib: 2.8.1 + + '@azure/microsoft-playwright-testing@1.0.0-beta.6(@playwright/test@1.49.1)': + dependencies: + '@azure/core-rest-pipeline': 1.18.2 + '@azure/identity': 4.5.0 + '@azure/logger': 1.1.4 + '@azure/storage-blob': 12.26.0 + '@playwright/test': 1.49.1 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/msal-browser@3.28.0': + dependencies: + '@azure/msal-common': 14.16.0 + + '@azure/msal-common@14.16.0': {} + + '@azure/msal-node@2.16.2': + dependencies: + '@azure/msal-common': 14.16.0 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + + '@azure/storage-blob@12.26.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-client': 1.9.2 + '@azure/core-http-compat': 2.1.2 + '@azure/core-lro': 2.7.2 + '@azure/core-paging': 1.6.2 + '@azure/core-rest-pipeline': 1.18.2 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/core-xml': 1.4.4 + '@azure/logger': 1.1.4 + events: 3.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + '@babel/code-frame@7.10.4': dependencies: '@babel/highlight': 7.25.9 @@ -28001,6 +28210,8 @@ snapshots: stickyfill@1.1.1: {} + stoppable@1.1.0: {} + storybook@8.4.7(prettier@3.4.2): dependencies: '@storybook/core': 8.4.7(prettier@3.4.2)