From 3bd97f895280b8c141283636226c42d973e95814 Mon Sep 17 00:00:00 2001 From: Jorge Calvar Date: Mon, 2 Feb 2026 12:09:27 +0100 Subject: [PATCH 1/6] test: add Playwright integration tests for dev-playground Add comprehensive frontend integration tests using Playwright to verify: - Page navigation and routing - Chart and data visualization rendering - SSE streaming and reconnection - Telemetry and SQL helpers functionality - Console error detection Tests use mocked API responses to verify UI behavior without backend dependencies. Includes test utilities, mock data fixtures, and Playwright configuration. Co-Authored-By: Claude Sonnet 4.5 --- apps/dev-playground/.gitignore | 3 + apps/dev-playground/README.md | 41 ++++ apps/dev-playground/client/package-lock.json | 31 ++- .../client/src/routes/telemetry.route.tsx | 4 +- apps/dev-playground/package.json | 6 +- apps/dev-playground/playwright.config.ts | 26 +++ .../tests/arrow-analytics.spec.ts | 101 ++++++++++ .../tests/data-visualization.spec.ts | 100 ++++++++++ .../tests/fixtures/mock-data.ts | 180 ++++++++++++++++++ .../tests/fixtures/test-utils.ts | 140 ++++++++++++++ apps/dev-playground/tests/navigation.spec.ts | 128 +++++++++++++ apps/dev-playground/tests/reconnect.spec.ts | 50 +++++ apps/dev-playground/tests/smoke.spec.ts | 78 ++++++++ apps/dev-playground/tests/sql-helpers.spec.ts | 76 ++++++++ apps/dev-playground/tests/telemetry.spec.ts | 36 ++++ pnpm-lock.yaml | 172 ++++++++++------- 16 files changed, 1097 insertions(+), 75 deletions(-) create mode 100644 apps/dev-playground/.gitignore create mode 100644 apps/dev-playground/README.md create mode 100644 apps/dev-playground/playwright.config.ts create mode 100644 apps/dev-playground/tests/arrow-analytics.spec.ts create mode 100644 apps/dev-playground/tests/data-visualization.spec.ts create mode 100644 apps/dev-playground/tests/fixtures/mock-data.ts create mode 100644 apps/dev-playground/tests/fixtures/test-utils.ts create mode 100644 apps/dev-playground/tests/navigation.spec.ts create mode 100644 apps/dev-playground/tests/reconnect.spec.ts create mode 100644 apps/dev-playground/tests/smoke.spec.ts create mode 100644 apps/dev-playground/tests/sql-helpers.spec.ts create mode 100644 apps/dev-playground/tests/telemetry.spec.ts diff --git a/apps/dev-playground/.gitignore b/apps/dev-playground/.gitignore new file mode 100644 index 00000000..5c957fcd --- /dev/null +++ b/apps/dev-playground/.gitignore @@ -0,0 +1,3 @@ +# Playwright +test-results/ +playwright-report/ \ No newline at end of file diff --git a/apps/dev-playground/README.md b/apps/dev-playground/README.md new file mode 100644 index 00000000..cf06ab12 --- /dev/null +++ b/apps/dev-playground/README.md @@ -0,0 +1,41 @@ +# Dev Playground + +Test application showing AppKit capabilities including analytics dashboards, SSE streaming, telemetry, and data visualization. + +## Development + +```bash +# Start development server +pnpm dev + +# Build for production +pnpm build + +# Start production server +pnpm start:local +``` + +## Integration Tests + +Integration tests use Playwright to verify the application works correctly with mocked backend responses. + +**Note:** These are frontend-only integration tests. API calls are intercepted at the browser level and return mock data, so the AppKit backend plugins are not tested. They focus on verifying UI behavior, navigation, data rendering, and client-side interactions. + +### Running Tests + +```bash +# Run all integration tests +pnpm test:integration + +# Run tests with interactive UI mode (for debugging) +pnpm test:integration:ui + +# Run tests in headed mode (see the browser) +pnpm test:integration:headed + +# Run a specific test file +npx playwright test tests/smoke.spec.ts + +# Run tests matching a pattern +npx playwright test -g "analytics" +``` diff --git a/apps/dev-playground/client/package-lock.json b/apps/dev-playground/client/package-lock.json index a0cd6cd5..22b20739 100644 --- a/apps/dev-playground/client/package-lock.json +++ b/apps/dev-playground/client/package-lock.json @@ -113,6 +113,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2615,6 +2616,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.134.9.tgz", "integrity": "sha512-JIxFamShs3gRIkOxpgz/3bglbSKZHMrzKASwNFg+sQPVXVPOLtN35D5PuEDAFTPPht9Wv48WWUNYE03ZytnNug==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/react-store": "^0.8.0", @@ -2722,6 +2724,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.134.9.tgz", "integrity": "sha512-9Vr8tYC59I70DYGVRknRf4vjQMjSfHvmc+iTM8vcpwERBh3Vgkv90f8ol85KHKqjorSsCqMeYFhFt8AM4A4CSw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/store": "^0.8.0", @@ -3061,6 +3064,7 @@ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.14.0" } @@ -3071,6 +3075,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3081,6 +3086,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3144,6 +3150,7 @@ "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", @@ -3402,6 +3409,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3625,6 +3633,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3923,7 +3932,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/d3-array": { "version": "3.2.4", @@ -4223,6 +4233,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5590,6 +5601,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5678,6 +5690,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5687,6 +5700,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5706,6 +5720,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -5874,7 +5889,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -6041,6 +6057,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -6193,7 +6210,8 @@ "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -6222,7 +6240,8 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -6268,6 +6287,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6364,6 +6384,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6653,6 +6674,7 @@ "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz", "integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==", "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", @@ -6745,6 +6767,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/apps/dev-playground/client/src/routes/telemetry.route.tsx b/apps/dev-playground/client/src/routes/telemetry.route.tsx index 5291da67..da07aadf 100644 --- a/apps/dev-playground/client/src/routes/telemetry.route.tsx +++ b/apps/dev-playground/client/src/routes/telemetry.route.tsx @@ -106,9 +106,7 @@ function TelemetryRoute() {
-

- Telemetry Demo -

+

Telemetry Demo

Demonstrates how the SDK's auto-instrumentation integrates with custom application telemetry. This example showcases HTTP and cache diff --git a/apps/dev-playground/package.json b/apps/dev-playground/package.json index 98b1f515..b5c3c0b8 100644 --- a/apps/dev-playground/package.json +++ b/apps/dev-playground/package.json @@ -14,7 +14,10 @@ "preview": "vite preview", "check": "tsc", "clean": "rm -rf build && cd client && rm -rf dist", - "clean:full": "rm -rf build node_modules && cd client && rm -rf dist node_modules" + "clean:full": "rm -rf build node_modules && cd client && rm -rf dist node_modules", + "test:integration": "playwright test", + "test:integration:ui": "playwright test --ui", + "test:integration:headed": "playwright test --headed" }, "keywords": [], "author": "", @@ -25,6 +28,7 @@ "zod": "^4.1.13" }, "devDependencies": { + "@playwright/test": "^1.50.1", "@types/node": "^20.0.0", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^5.0.4", diff --git a/apps/dev-playground/playwright.config.ts b/apps/dev-playground/playwright.config.ts new file mode 100644 index 00000000..b0ca6f92 --- /dev/null +++ b/apps/dev-playground/playwright.config.ts @@ -0,0 +1,26 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + baseURL: "http://localhost:8000", + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: { + command: "pnpm dev", + url: "http://localhost:8000", + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, +}); diff --git a/apps/dev-playground/tests/arrow-analytics.spec.ts b/apps/dev-playground/tests/arrow-analytics.spec.ts new file mode 100644 index 00000000..497d29bd --- /dev/null +++ b/apps/dev-playground/tests/arrow-analytics.spec.ts @@ -0,0 +1,101 @@ +import { expect, test } from "@playwright/test"; +import { + STRICT_MODE_MULTIPLIER, + setupMockAPI, + trackApiCalls, + waitForChartsToLoad, + waitForPageLoad, +} from "./fixtures/test-utils"; + +test.describe("Arrow Analytics", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("page loads and displays heading", async ({ page }) => { + await page.goto("/arrow-analytics"); + await waitForPageLoad(page); + await expect(page.getByText("Unified Charts API")).toBeVisible(); + }); + + test("calls expected API endpoints", async ({ page }) => { + const appsListCalls = trackApiCalls(page, "apps_list"); + const spendDataCalls = trackApiCalls(page, "spend_data"); + const topContributorsCalls = trackApiCalls(page, "top_contributors"); + const heatmapCalls = trackApiCalls(page, "app_activity_heatmap"); + + await page.goto("/arrow-analytics"); + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await waitForChartsToLoad(page); + + expect(appsListCalls.length).toBe(5 * STRICT_MODE_MULTIPLIER); + expect(spendDataCalls.length).toBe(5 * STRICT_MODE_MULTIPLIER); + expect(topContributorsCalls.length).toBe(2 * STRICT_MODE_MULTIPLIER); + expect(heatmapCalls.length).toBe(2 * STRICT_MODE_MULTIPLIER); + }); + + test("charts render with mock data (no empty states)", async ({ page }) => { + await page.goto("/arrow-analytics"); + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await waitForChartsToLoad(page); + + const barCharts = page.locator('[data-testid="bar-chart-apps_list"]'); + expect(await barCharts.count()).toBe(3); + await expect(barCharts.first().locator("canvas")).toBeVisible(); + + const lineCharts = page.locator('[data-testid="line-chart-spend_data"]'); + expect(await lineCharts.count()).toBe(3); + await expect(lineCharts.first().locator("canvas")).toBeVisible(); + + const donutCharts = page.locator( + '[data-testid="donut-chart-top_contributors"]', + ); + expect(await donutCharts.count()).toBe(2); + await expect(donutCharts.first().locator("canvas")).toBeVisible(); + + const heatmapCharts = page.locator( + '[data-testid="heatmap-chart-app_activity_heatmap"]', + ); + expect(await heatmapCharts.count()).toBe(2); + await expect(heatmapCharts.first().locator("canvas")).toBeVisible(); + }); + + test("chart tooltip appears on hover with mock app data", async ({ + page, + }) => { + await page.goto("/arrow-analytics"); + await waitForChartsToLoad(page); + + const barChart = page + .locator('[data-testid="bar-chart-apps_list"]') + .first() + .locator("canvas"); + await expect(barChart).toBeVisible(); + + const box = await barChart.boundingBox(); + if (!box) throw new Error("Could not get chart bounding box"); + + // Try multiple positions to find a data point (bar positions vary by chart size) + const positions = [0.2, 0.35, 0.5, 0.65, 0.8]; + for (const xRatio of positions) { + await page.mouse.move( + box.x + box.width * xRatio, + box.y + box.height * 0.4, + ); + + // Check if tooltip appeared with any of our mock app names + const tooltip = page + .locator("div") + .filter({ hasText: /App One|App Two|App Three/ }); + + try { + await expect(tooltip.first()).toBeVisible({ timeout: 1000 }); + return; // Test passed - tooltip found + } catch {} + } + + throw new Error( + "Could not trigger tooltip with any mock app data after trying multiple positions", + ); + }); +}); diff --git a/apps/dev-playground/tests/data-visualization.spec.ts b/apps/dev-playground/tests/data-visualization.spec.ts new file mode 100644 index 00000000..07749bda --- /dev/null +++ b/apps/dev-playground/tests/data-visualization.spec.ts @@ -0,0 +1,100 @@ +import { expect, test } from "@playwright/test"; +import { + STRICT_MODE_MULTIPLIER, + setupMockAPI, + trackApiCalls, + waitForPageLoad, +} from "./fixtures/test-utils"; + +test.describe("Data Visualization Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("data-visualization page loads successfully", async ({ page }) => { + await page.goto("/data-visualization"); + await waitForPageLoad(page); + + await expect(page).toHaveURL("/data-visualization"); + }); + + test("page displays Data Visualization heading", async ({ page }) => { + await page.goto("/data-visualization"); + await waitForPageLoad(page); + + await expect(page.getByText("Data Visualization")).toBeVisible(); + }); + + test("simple data table displays mock data", async ({ page }) => { + await page.goto("/data-visualization"); + await waitForPageLoad(page); + + // The simple table is the first table on the page + const simpleTable = page.locator("table").nth(0); + + await simpleTable.scrollIntoViewIfNeeded(); + await expect(simpleTable).toBeVisible(); + + // Verify the table contains expected mock data cells + await expect( + simpleTable.getByRole("cell", { name: "Untagged App 1" }), + ).toBeVisible(); + await expect( + simpleTable.getByRole("cell", { name: "user4@databricks.com" }), + ).toBeVisible(); + }); + + test("advanced data table displays mock data", async ({ page }) => { + await page.goto("/data-visualization"); + await waitForPageLoad(page); + + // The advanced table is the second table on the page + const advancedTable = page.locator("table").nth(1); + + await advancedTable.scrollIntoViewIfNeeded(); + await expect(advancedTable).toBeVisible(); + + // Verify the table contains expected mock data cells + await expect( + advancedTable.getByRole("cell", { name: "Untagged App 2" }), + ).toBeVisible(); + await expect( + advancedTable.getByRole("cell", { name: "user5@databricks.com" }), + ).toBeVisible(); + }); + + test("calls expected API endpoints on page load", async ({ page }) => { + const untaggedAppsCalls = trackApiCalls(page, "untagged_apps"); + const spendDataCalls = trackApiCalls(page, "spend_data"); + const topContributorsCalls = trackApiCalls(page, "top_contributors"); + + await page.goto("/data-visualization"); + await waitForPageLoad(page); + + // Scroll to load all charts and tables + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForLoadState("networkidle"); + + // Verify API calls: 2 tables use untagged_apps + expect(untaggedAppsCalls.length).toBe(2 * STRICT_MODE_MULTIPLIER); + // Multiple charts use spend_data (AreaChart x2, LineChart x2, RadarChart x2) + expect(spendDataCalls.length).toBe(6 * STRICT_MODE_MULTIPLIER); + // BarChart x2, PieChart x2 use top_contributors + expect(topContributorsCalls.length).toBe(4 * STRICT_MODE_MULTIPLIER); + }); + + test("can toggle code visibility", async ({ page }) => { + await page.goto("/data-visualization"); + await waitForPageLoad(page); + + const showCodeButton = page + .getByRole("button", { name: "Show Code" }) + .first(); + await showCodeButton.click(); + + // Verify code section is revealed by checking the Hide Code button appears + await expect( + page.getByRole("button", { name: "Hide Code" }).first(), + ).toBeVisible(); + }); +}); diff --git a/apps/dev-playground/tests/fixtures/mock-data.ts b/apps/dev-playground/tests/fixtures/mock-data.ts new file mode 100644 index 00000000..39a6adc0 --- /dev/null +++ b/apps/dev-playground/tests/fixtures/mock-data.ts @@ -0,0 +1,180 @@ +/** + * Mock data for dev-playground frontend integration tests + * These mock responses simulate the API responses from the backend + */ + +// Analytics mock data +export const mockAnalyticsData = { + spendSummary: [ + { + total: 15000, + average: 500, + forecasted: 18000, + }, + ], + + appsList: [ + { + id: 1, + name: "App One", + creator: "user1@databricks.com", + totalSpend: 5000, + tags: ["prod", "analytics"], + createdAt: "2024-01-15T10:30:00Z", + }, + { + id: 2, + name: "App Two", + creator: "user2@databricks.com", + totalSpend: 3500, + tags: ["staging"], + createdAt: "2024-02-20T14:45:00Z", + }, + { + id: 3, + name: "App Three", + creator: "user3@databricks.com", + totalSpend: 2200, + tags: [], + createdAt: "2024-03-10T09:00:00Z", + }, + ], + + untaggedApps: [ + { + app_name: "Untagged App 1", + creator: "user4@databricks.com", + total_cost_usd: 1200, + avg_period_cost_usd: 400, + }, + { + app_name: "Untagged App 2", + creator: "user5@databricks.com", + total_cost_usd: 800, + avg_period_cost_usd: 266, + }, + ], + + spendData: [ + { + aggregation_period: "2024-01-01", + cost_usd: 1200, + group_key: "default", + }, + { + aggregation_period: "2024-01-02", + cost_usd: 1350, + group_key: "default", + }, + { + aggregation_period: "2024-01-03", + cost_usd: 980, + group_key: "default", + }, + { + aggregation_period: "2024-01-04", + cost_usd: 1500, + group_key: "default", + }, + { + aggregation_period: "2024-01-05", + cost_usd: 1100, + group_key: "default", + }, + ], + + topContributors: [ + { app_name: "Top App 1", total_cost_usd: 5000 }, + { app_name: "Top App 2", total_cost_usd: 3500 }, + { app_name: "Top App 3", total_cost_usd: 2800 }, + { app_name: "Top App 4", total_cost_usd: 2200 }, + { app_name: "Top App 5", total_cost_usd: 1500 }, + ], + + appActivityHeatmap: [ + { app_name: "App One", day_of_week: "Monday", spend: 500 }, + { app_name: "App One", day_of_week: "Tuesday", spend: 600 }, + { app_name: "App One", day_of_week: "Wednesday", spend: 450 }, + { app_name: "App Two", day_of_week: "Monday", spend: 300 }, + { app_name: "App Two", day_of_week: "Tuesday", spend: 400 }, + { app_name: "App Two", day_of_week: "Wednesday", spend: 350 }, + ], + + sqlHelpersTest: [ + { + string_value: "Hello, Databricks!", + number_value: 42, + boolean_value: true, + date_value: "2024-01-15", + timestamp_value: "2024-01-15T10:30:00Z", + binary_value: "Spark", + binary_hex: "537061726B", + binary_length: 5, + }, + ], +}; + +// Reconnect/SSE mock data +export const mockReconnectMessages = [ + { + type: "message", + count: 1, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 1 of 5", + }, + { + type: "message", + count: 2, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 2 of 5", + }, + { + type: "message", + count: 3, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 3 of 5", + }, + { + type: "message", + count: 4, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 4 of 5", + }, + { + type: "message", + count: 5, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 5 of 5", + }, +]; + +// Telemetry mock data +export const mockTelemetryResponse = { + success: true, + message: "Telemetry example completed successfully", + duration_ms: 150, + result: { items_processed: 5 }, + tracing: { + hint: "View traces in Grafana", + services: ["telemetry-examples"], + expectedSpans: [ + "telemetry-examples.combined", + "cache-lookup", + "http-request", + ], + }, + metrics: { + recorded: [ + "telemetry_examples.requests_total", + "telemetry_examples.request_duration_ms", + ], + }, + logs: { + emitted: ["Processing started", "Cache miss", "Processing completed"], + }, +}; diff --git a/apps/dev-playground/tests/fixtures/test-utils.ts b/apps/dev-playground/tests/fixtures/test-utils.ts new file mode 100644 index 00000000..dbe6d280 --- /dev/null +++ b/apps/dev-playground/tests/fixtures/test-utils.ts @@ -0,0 +1,140 @@ +import type { Page, Request } from "@playwright/test"; +import { + mockAnalyticsData, + mockReconnectMessages, + mockTelemetryResponse, +} from "./mock-data"; + +/** + * React 19 Strict Mode doubles useEffect invocations in development mode + * to help detect side effects. This multiplier accounts for that behavior + * when asserting API call counts in tests. + * + * @see https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-re-running-effects-in-development + */ +export const STRICT_MODE_MULTIPLIER = 2; + +function createSSEResponse(data: unknown): string { + const event = JSON.stringify({ type: "result", data }); + return `data: ${event}\n\n`; +} + +function getSSEHeaders() { + return { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }; +} + +export async function setupMockAPI(page: Page) { + await page.route("**/api/analytics/query/**", async (route) => { + const url = route.request().url(); + + if (url.includes("spend_summary")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.spendSummary), + }); + } + if (url.includes("apps_list")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.appsList), + }); + } + if (url.includes("untagged_apps")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.untaggedApps), + }); + } + if (url.includes("spend_data")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.spendData), + }); + } + if (url.includes("top_contributors")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.topContributors), + }); + } + if (url.includes("app_activity_heatmap")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.appActivityHeatmap), + }); + } + if (url.includes("sql_helpers_test")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.sqlHelpersTest), + }); + } + + // Default empty response for unknown queries + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse([]), + }); + }); + + await page.route("**/api/reconnect/stream**", async (route) => { + const body = mockReconnectMessages + .map((msg, i) => `id: ${i + 1}\ndata: ${JSON.stringify(msg)}\n\n`) + .join(""); + + return route.fulfill({ + status: 200, + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + body, + }); + }); + + await page.route("**/api/reconnect", async (route) => { + if (route.request().url().endsWith("/api/reconnect")) { + return route.fulfill({ json: { message: "Reconnected" } }); + } + return route.continue(); + }); + + await page.route("**/api/telemetry-examples/**", async (route) => { + return route.fulfill({ json: mockTelemetryResponse }); + }); +} + +export async function waitForPageLoad(page: Page) { + await page.waitForLoadState("networkidle"); +} + +export async function waitForChartsToLoad(page: Page) { + await waitForPageLoad(page); + await page.waitForFunction( + () => document.querySelectorAll(".animate-pulse").length === 0, + { timeout: 10000 }, + ); +} + +export function trackApiCalls(page: Page, urlPattern: string) { + const requests: Request[] = []; + page.on("request", (request) => { + if (request.url().includes(urlPattern)) { + requests.push(request); + } + }); + return requests; +} diff --git a/apps/dev-playground/tests/navigation.spec.ts b/apps/dev-playground/tests/navigation.spec.ts new file mode 100644 index 00000000..e761488a --- /dev/null +++ b/apps/dev-playground/tests/navigation.spec.ts @@ -0,0 +1,128 @@ +import { expect, test } from "@playwright/test"; +import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; + +const homepageNavigationTests: Array<{ + name: string; + buttonName: string; + buttonIndex?: number; + expectedUrl: string; +}> = [ + { + name: "analytics", + buttonName: "Explore real-time analytics", + buttonIndex: 0, + expectedUrl: "/analytics", + }, + { + name: "arrow-analytics", + buttonName: "Explore real-time analytics", + buttonIndex: 1, + expectedUrl: "/arrow-analytics", + }, + { + name: "reconnect", + buttonName: "View Reconnect Demo", + expectedUrl: "/reconnect", + }, + { + name: "data-visualization", + buttonName: "Explore data visualization", + expectedUrl: "/data-visualization", + }, + { + name: "telemetry", + buttonName: "Try Telemetry Examples", + expectedUrl: "/telemetry", + }, + { + name: "sql-helpers", + buttonName: "Try SQL Helpers", + expectedUrl: "/sql-helpers", + }, + { + name: "type-safety", + buttonName: "Explore Type Safety", + expectedUrl: "/type-safety", + }, +]; + +test.describe("Navigation Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + for (const { + name, + buttonName, + buttonIndex, + expectedUrl, + } of homepageNavigationTests) { + test(`can navigate to ${name} from homepage`, async ({ page }) => { + await page.goto("/"); + await waitForPageLoad(page); + + const button = page.getByRole("button", { name: buttonName }); + if (buttonIndex !== undefined) { + await button.nth(buttonIndex).click(); + } else { + await button.click(); + } + + await expect(page).toHaveURL(expectedUrl); + }); + } + + test("navigation bar shows on non-home pages", async ({ page }) => { + await page.goto("/analytics"); + await waitForPageLoad(page); + + await expect( + page.getByRole("link", { name: "AppKit Playground" }), + ).toBeVisible(); + + await expect( + page.getByRole("button", { name: "Analytics", exact: true }), + ).toBeVisible(); + await expect( + page.getByRole("button", { name: "Arrow Analytics" }), + ).toBeVisible(); + await expect(page.getByRole("button", { name: "Reconnect" })).toBeVisible(); + await expect(page.getByRole("button", { name: "Telemetry" })).toBeVisible(); + await expect( + page.getByRole("button", { name: "SQL Helpers" }), + ).toBeVisible(); + }); + + test("can navigate back to home from nav bar", async ({ page }) => { + await page.goto("/analytics"); + await waitForPageLoad(page); + + await page.getByRole("link", { name: "AppKit Playground" }).click(); + + await expect(page).toHaveURL("/"); + }); + + test("can navigate between pages using nav bar", async ({ page }) => { + await page.goto("/analytics"); + await waitForPageLoad(page); + + await page.getByRole("button", { name: "Reconnect" }).click(); + await expect(page).toHaveURL("/reconnect"); + + await page.getByRole("button", { name: "Telemetry" }).click(); + await expect(page).toHaveURL("/telemetry"); + + await page.getByRole("button", { name: "SQL Helpers" }).click(); + await expect(page).toHaveURL("/sql-helpers"); + }); + + test("navigation bar is hidden on homepage", async ({ page }) => { + await page.goto("/"); + await waitForPageLoad(page); + + const navBar = page.locator("nav").filter({ + has: page.getByRole("button", { name: "Analytics" }), + }); + await expect(navBar).not.toBeVisible(); + }); +}); diff --git a/apps/dev-playground/tests/reconnect.spec.ts b/apps/dev-playground/tests/reconnect.spec.ts new file mode 100644 index 00000000..6d3a9a82 --- /dev/null +++ b/apps/dev-playground/tests/reconnect.spec.ts @@ -0,0 +1,50 @@ +import { expect, test } from "@playwright/test"; +import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; + +test.describe("Reconnect Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("reconnect page loads successfully", async ({ page }) => { + await page.goto("/reconnect"); + await waitForPageLoad(page); + + await expect(page).toHaveURL("/reconnect"); + }); + + test("connects to SSE stream and receives all messages", async ({ page }) => { + const streamRequestPromise = page.waitForRequest((request) => + request.url().includes("/api/reconnect/stream"), + ); + + await page.goto("/reconnect"); + await streamRequestPromise; + await waitForPageLoad(page); + + await expect( + page.locator('[data-slot="badge"]').filter({ hasText: "Reconnected" }), + ).toBeVisible({ timeout: 5000 }); + + const messageCountContainer = page.locator("div").filter({ + has: page.getByText("/ 5 messages received"), + }); + await expect(messageCountContainer.locator("h2")).toHaveText("5", { + timeout: 5000, + }); + }); + + test("restart button triggers new stream connection", async ({ page }) => { + await page.goto("/reconnect"); + await waitForPageLoad(page); + + const newStreamRequestPromise = page.waitForRequest((request) => + request.url().includes("/api/reconnect/stream"), + ); + + const restartButton = page.getByRole("button", { name: /restart/i }); + await restartButton.click(); + + await newStreamRequestPromise; + }); +}); diff --git a/apps/dev-playground/tests/smoke.spec.ts b/apps/dev-playground/tests/smoke.spec.ts new file mode 100644 index 00000000..dcd9aa66 --- /dev/null +++ b/apps/dev-playground/tests/smoke.spec.ts @@ -0,0 +1,78 @@ +import { expect, test } from "@playwright/test"; +import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; + +test.describe("Smoke Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("app loads and displays homepage", async ({ page }) => { + await page.goto("/"); + await waitForPageLoad(page); + + await expect( + page.getByRole("heading", { name: "AppKit Playground" }), + ).toBeVisible(); + + await expect( + page.getByText("Explore the capabilities of the AppKit"), + ).toBeVisible(); + }); + + test("all feature cards are visible on homepage", async ({ page }) => { + await page.goto("/"); + await waitForPageLoad(page); + + await expect( + page.getByText("Analytics Dashboard", { exact: true }), + ).toBeVisible(); + await expect( + page.getByText("Arrow Analytics Dashboard", { exact: true }), + ).toBeVisible(); + await expect( + page.getByText("Stream Reconnection", { exact: true }), + ).toBeVisible(); + await expect( + page.getByText("Data Visualization", { exact: true }), + ).toBeVisible(); + await expect(page.getByText("Telemetry", { exact: true })).toBeVisible(); + await expect(page.getByText("SQL Helpers", { exact: true })).toBeVisible(); + await expect( + page.getByText("Type-Safe SQL", { exact: true }), + ).toBeVisible(); + }); + + test("theme selector is visible on homepage", async ({ page }) => { + await page.goto("/"); + await waitForPageLoad(page); + + const themeButton = page.locator('[class*="top-4"][class*="right-4"]'); + await expect(themeButton).toBeVisible(); + }); + + test("no console errors on page load", async ({ page }) => { + const consoleErrors: string[] = []; + + page.on("console", (msg) => { + if (msg.type() === "error") { + consoleErrors.push(msg.text()); + } + }); + + await page.goto("/"); + await waitForPageLoad(page); + + expect( + consoleErrors, + `Console errors detected: ${consoleErrors.join(", ")}`, + ).toHaveLength(0); + }); + + test("page has a title", async ({ page }) => { + await page.goto("/"); + await waitForPageLoad(page); + + const title = await page.title(); + expect(title).toBeTruthy(); + }); +}); diff --git a/apps/dev-playground/tests/sql-helpers.spec.ts b/apps/dev-playground/tests/sql-helpers.spec.ts new file mode 100644 index 00000000..5e31b8cd --- /dev/null +++ b/apps/dev-playground/tests/sql-helpers.spec.ts @@ -0,0 +1,76 @@ +import { expect, test } from "@playwright/test"; +import { mockAnalyticsData } from "./fixtures/mock-data"; +import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; + +test.describe("SQL Helpers Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("sql-helpers page loads successfully", async ({ page }) => { + await page.goto("/sql-helpers"); + await waitForPageLoad(page); + + await expect(page).toHaveURL("/sql-helpers"); + }); + + test("can interact with string input", async ({ page }) => { + await page.goto("/sql-helpers"); + await waitForPageLoad(page); + + const inputs = page.locator("input").first(); + await inputs.fill("Test String Value"); + + await expect(inputs).toHaveValue("Test String Value"); + }); + + test("can interact with number input", async ({ page }) => { + await page.goto("/sql-helpers"); + await waitForPageLoad(page); + + const numberInput = page.locator('input[type="number"]').first(); + await numberInput.fill("123"); + + await expect(numberInput).toHaveValue("123"); + }); + + test("can toggle boolean value", async ({ page }) => { + await page.goto("/sql-helpers"); + await waitForPageLoad(page); + + const falseButton = page.getByRole("button", { name: "false" }); + await falseButton.click(); + + const trueButton = page.getByRole("button", { name: "true" }); + await expect(trueButton).toBeVisible(); + }); + + test("show code button reveals code example", async ({ page }) => { + await page.goto("/sql-helpers"); + await waitForPageLoad(page); + + const showCodeButton = page + .getByRole("button", { name: "Show Code" }) + .first(); + await showCodeButton.click(); + + await expect(page.getByText("Usage:").first()).toBeVisible(); + }); + + test("sql_helpers_test query executes and displays mock data", async ({ + page, + }) => { + await page.goto("/sql-helpers"); + await waitForPageLoad(page); + + await expect(page.getByText("Query executed successfully")).toBeVisible({ + timeout: 5000, + }); + + const resultPre = page.locator(".bg-success\\/10 pre"); + await expect(resultPre).toBeVisible(); + await expect(resultPre).toHaveText( + JSON.stringify(mockAnalyticsData.sqlHelpersTest[0], null, 2), + ); + }); +}); diff --git a/apps/dev-playground/tests/telemetry.spec.ts b/apps/dev-playground/tests/telemetry.spec.ts new file mode 100644 index 00000000..d81b26ea --- /dev/null +++ b/apps/dev-playground/tests/telemetry.spec.ts @@ -0,0 +1,36 @@ +import { expect, test } from "@playwright/test"; +import { + setupMockAPI, + trackApiCalls, + waitForPageLoad, +} from "./fixtures/test-utils"; + +test.describe("Telemetry Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("telemetry page loads successfully", async ({ page }) => { + await page.goto("/telemetry"); + await waitForPageLoad(page); + + await expect(page).toHaveURL("/telemetry"); + }); + + test("run button triggers POST request and shows success", async ({ + page, + }) => { + const requests = trackApiCalls(page, "/api/telemetry-examples"); + + await page.goto("/telemetry"); + await waitForPageLoad(page); + + const runButton = page.getByRole("button", { name: /Run.*Request/i }); + await runButton.click(); + + await expect(page.getByText("Success")).toBeVisible({ timeout: 5000 }); + + const postRequests = requests.filter((r) => r.method() === "POST"); + expect(postRequests.length).toBeGreaterThan(0); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d63a0a85..b393819e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,6 +136,9 @@ importers: specifier: ^4.1.13 version: 4.1.13 devDependencies: + '@playwright/test': + specifier: ^1.50.1 + version: 1.58.0 '@types/node': specifier: ^20.0.0 version: 20.19.21 @@ -3000,8 +3003,8 @@ packages: resolution: {integrity: sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.108.0': - resolution: {integrity: sha512-7lf13b2IA/kZO6xgnIZA88sq3vwrxWk+2vxf6cc+omwYCRTiA5e63Beqf3fz/v8jEviChWWmFYBwzfSeyrsj7Q==} + '@oxc-project/types@0.110.0': + resolution: {integrity: sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow==} '@oxc-project/types@0.93.0': resolution: {integrity: sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==} @@ -3014,6 +3017,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.58.0': + resolution: {integrity: sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==} + engines: {node: '>=18'} + hasBin: true + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -3721,8 +3729,8 @@ packages: cpu: [arm64] os: [android] - '@rolldown/binding-android-arm64@1.0.0-beta.60': - resolution: {integrity: sha512-hOW6iQXtpG4uCW1zGK56+KhEXGttSkTp2ykncW/nkOIF/jOKTqbM944Q73HVeMXP1mPRvE2cZwNp3xeLIeyIGQ==} + '@rolldown/binding-android-arm64@1.0.0-rc.1': + resolution: {integrity: sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] @@ -3733,8 +3741,8 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-arm64@1.0.0-beta.60': - resolution: {integrity: sha512-vyDA4HXY2mP8PPtl5UE17uGPxUNG4m1wkfa3kAkR8JWrFbarV97UmLq22IWrNhtBPa89xqerzLK8KoVmz5JqCQ==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.1': + resolution: {integrity: sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] @@ -3745,8 +3753,8 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.60': - resolution: {integrity: sha512-WnxyqxAKP2BsxouwGY/RCF5UFw/LA4QOHhJ7VEl+UCelHokiwqNHRbryLAyRy3TE1FZ5eae+vAFcaetAu/kWLw==} + '@rolldown/binding-darwin-x64@1.0.0-rc.1': + resolution: {integrity: sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] @@ -3757,8 +3765,8 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-freebsd-x64@1.0.0-beta.60': - resolution: {integrity: sha512-JtyWJ+zXOHof5gOUYwdTWI2kL6b8q9eNwqB/oD4mfUFaC/COEB2+47JMhcq78dey9Ahmec3DZKRDZPRh9hNAMQ==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.1': + resolution: {integrity: sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] @@ -3769,8 +3777,8 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.60': - resolution: {integrity: sha512-LrMoKqpHx+kCaNSk84iSBd4yVOymLIbxJQtvFjDN2CjQraownR+IXcwYDblFcj9ivmS54T3vCboXBbm3s1zbPQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': + resolution: {integrity: sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -3781,8 +3789,8 @@ packages: cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.60': - resolution: {integrity: sha512-sqI+Vdx1gmXJMsXN3Fsewm3wlt7RHvRs1uysSp//NLsCoh9ZFEUr4ZzGhWKOg6Rvf+njNu/vCsz96x7wssLejQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': + resolution: {integrity: sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3793,8 +3801,8 @@ packages: cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.60': - resolution: {integrity: sha512-8xlqGLDtTP8sBfYwneTDu8+PRm5reNEHAuI/+6WPy9y350ls0KTFd3EJCOWEXWGW0F35ko9Fn9azmurBTjqOrQ==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': + resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3805,8 +3813,8 @@ packages: cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.60': - resolution: {integrity: sha512-iR4nhVouVZK1CiGGGyz+prF5Lw9Lmz30Rl36Hajex+dFVFiegka604zBwzTp5Tl0BZnr50ztnVJ30tGrBhDr8Q==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': + resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -3817,8 +3825,8 @@ packages: cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.60': - resolution: {integrity: sha512-HbfNcqNeqxFjSMf1Kpe8itr2e2lr0Bm6HltD2qXtfU91bSSikVs9EWsa1ThshQ1v2ZvxXckGjlVLtah6IoslPg==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': + resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -3829,8 +3837,8 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.60': - resolution: {integrity: sha512-BiiamFcgTJ+ZFOUIMO9AHXUo9WXvHVwGfSrJ+Sv0AsTd2w3VN7dJGiH3WRcxKFetljJHWvGbM4fdpY5lf6RIvw==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': + resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] @@ -3840,8 +3848,8 @@ packages: engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.60': - resolution: {integrity: sha512-6roXGbHMdR2ucnxXuwbmQvk8tuYl3VGu0yv13KxspyKBxxBd4RS6iykzLD6mX2gMUHhfX8SVWz7n/62gfyKHow==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': + resolution: {integrity: sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw==} engines: {node: '>=14.0.0'} cpu: [wasm32] @@ -3851,8 +3859,8 @@ packages: cpu: [arm64] os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.60': - resolution: {integrity: sha512-JBOm8/DC/CKnHyMHoJFdvzVHxUixid4dGkiTqGflxOxO43uSJMpl77pSPXvzwZ/VXwqblU2V0/PanyCBcRLowQ==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': + resolution: {integrity: sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] @@ -3869,8 +3877,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.60': - resolution: {integrity: sha512-MKF0B823Efp+Ot8KsbwIuGhKH58pf+2rSM6VcqyNMlNBHheOM0Gf7JmEu+toc1jgN6fqjH7Et+8hAzsLVkIGfA==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': + resolution: {integrity: sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3884,8 +3892,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} - '@rolldown/pluginutils@1.0.0-beta.60': - resolution: {integrity: sha512-Jz4aqXRPVtqkH1E3jRDzLO5cgN5JwW+WG0wXGE4NiJd25nougv/AHzxmKCzmVQUYnxLmTM0M4wrZp+LlC2FKLg==} + '@rolldown/pluginutils@1.0.0-rc.1': + resolution: {integrity: sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA==} '@rollup/rollup-android-arm-eabi@4.52.4': resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} @@ -6706,6 +6714,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -8695,6 +8708,16 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + playwright-core@1.58.0: + resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.0: + resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==} + engines: {node: '>=18'} + hasBin: true + plop@4.0.4: resolution: {integrity: sha512-YdxtHWcPV8hDsszVPr4VQBVGNdn5ZQmEW+cZakZkuVeQHtENmrtY4AhuyoZW6s7ZjpmrS+llLQrfDgRKNQNsmg==} engines: {node: '>=18'} @@ -9641,8 +9664,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.0.0-beta.60: - resolution: {integrity: sha512-YYgpv7MiTp9LdLj1fzGzCtij8Yi2OKEc3HQtfbIxW4yuSgpQz9518I69U72T5ErPA/ATOXqlcisiLrWy+5V9YA==} + rolldown@1.0.0-rc.1: + resolution: {integrity: sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -14578,7 +14601,7 @@ snapshots: '@oxc-project/runtime@0.92.0': {} - '@oxc-project/types@0.108.0': {} + '@oxc-project/types@0.110.0': {} '@oxc-project/types@0.93.0': {} @@ -14587,6 +14610,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.58.0': + dependencies: + playwright: 1.58.0 + '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -15322,61 +15349,61 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-android-arm64@1.0.0-beta.60': + '@rolldown/binding-android-arm64@1.0.0-rc.1': optional: true '@rolldown/binding-darwin-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.60': + '@rolldown/binding-darwin-arm64@1.0.0-rc.1': optional: true '@rolldown/binding-darwin-x64@1.0.0-beta.41': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.60': + '@rolldown/binding-darwin-x64@1.0.0-rc.1': optional: true '@rolldown/binding-freebsd-x64@1.0.0-beta.41': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.60': + '@rolldown/binding-freebsd-x64@1.0.0-rc.1': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.60': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.60': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': optional: true '@rolldown/binding-linux-arm64-musl@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.60': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': optional: true '@rolldown/binding-linux-x64-gnu@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.60': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': optional: true '@rolldown/binding-linux-x64-musl@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.60': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': optional: true '@rolldown/binding-openharmony-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.60': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': optional: true '@rolldown/binding-wasm32-wasi@1.0.0-beta.41': @@ -15384,7 +15411,7 @@ snapshots: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.60': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true @@ -15392,7 +15419,7 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.60': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': optional: true '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41': @@ -15401,7 +15428,7 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.0.0-beta.41': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.60': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': optional: true '@rolldown/pluginutils@1.0.0-beta.38': {} @@ -15410,7 +15437,7 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.47': {} - '@rolldown/pluginutils@1.0.0-beta.60': {} + '@rolldown/pluginutils@1.0.0-rc.1': {} '@rollup/rollup-android-arm-eabi@4.52.4': optional: true @@ -18556,6 +18583,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -20975,6 +21005,14 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-core@1.58.0: {} + + playwright@1.58.0: + dependencies: + playwright-core: 1.58.0 + optionalDependencies: + fsevents: 2.3.2 + plop@4.0.4(@types/node@24.7.2): dependencies: '@types/liftoff': 4.0.3 @@ -22033,7 +22071,7 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-plugin-dts@0.16.11(rolldown@1.0.0-beta.60)(typescript@5.9.3): + rolldown-plugin-dts@0.16.11(rolldown@1.0.0-rc.1)(typescript@5.9.3): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.5 @@ -22044,7 +22082,7 @@ snapshots: dts-resolver: 2.1.2 get-tsconfig: 4.12.0 magic-string: 0.30.19 - rolldown: 1.0.0-beta.60 + rolldown: 1.0.0-rc.1 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -22108,24 +22146,24 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.41 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.41 - rolldown@1.0.0-beta.60: + rolldown@1.0.0-rc.1: dependencies: - '@oxc-project/types': 0.108.0 - '@rolldown/pluginutils': 1.0.0-beta.60 + '@oxc-project/types': 0.110.0 + '@rolldown/pluginutils': 1.0.0-rc.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.60 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.60 - '@rolldown/binding-darwin-x64': 1.0.0-beta.60 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.60 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.60 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.60 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.60 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.60 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.60 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.60 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.60 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.60 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.60 + '@rolldown/binding-android-arm64': 1.0.0-rc.1 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.1 + '@rolldown/binding-darwin-x64': 1.0.0-rc.1 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.1 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.1 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.1 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.1 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.1 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.1 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.1 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.1 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.1 rollup@4.52.4: dependencies: @@ -22773,8 +22811,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.60 - rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-beta.60)(typescript@5.9.3) + rolldown: 1.0.0-rc.1 + rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-rc.1)(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.1 tinyglobby: 0.2.15 From a9f779534c67f05e005431b878686de86452bfab Mon Sep 17 00:00:00 2001 From: Jorge Calvar Date: Thu, 5 Feb 2026 12:35:51 +0100 Subject: [PATCH 2/6] test: apply PR feedback - remove unnecessary tests and reorganize utils - Remove navigation.spec.ts (tests router, not AppKit) - Remove 3 tests from smoke.spec.ts (test Dev Playground, not AppKit) - Move test utilities from fixtures/ to utils/ Signed-off-by: Jorge Calvar --- .../tests/arrow-analytics.spec.ts | 2 +- .../tests/data-visualization.spec.ts | 2 +- apps/dev-playground/tests/navigation.spec.ts | 128 ------------------ apps/dev-playground/tests/reconnect.spec.ts | 2 +- apps/dev-playground/tests/smoke.spec.ts | 45 +----- apps/dev-playground/tests/sql-helpers.spec.ts | 4 +- apps/dev-playground/tests/telemetry.spec.ts | 2 +- .../tests/{fixtures => utils}/mock-data.ts | 0 .../tests/{fixtures => utils}/test-utils.ts | 0 9 files changed, 7 insertions(+), 178 deletions(-) delete mode 100644 apps/dev-playground/tests/navigation.spec.ts rename apps/dev-playground/tests/{fixtures => utils}/mock-data.ts (100%) rename apps/dev-playground/tests/{fixtures => utils}/test-utils.ts (100%) diff --git a/apps/dev-playground/tests/arrow-analytics.spec.ts b/apps/dev-playground/tests/arrow-analytics.spec.ts index 497d29bd..7b269d99 100644 --- a/apps/dev-playground/tests/arrow-analytics.spec.ts +++ b/apps/dev-playground/tests/arrow-analytics.spec.ts @@ -5,7 +5,7 @@ import { trackApiCalls, waitForChartsToLoad, waitForPageLoad, -} from "./fixtures/test-utils"; +} from "./utils/test-utils"; test.describe("Arrow Analytics", () => { test.beforeEach(async ({ page }) => { diff --git a/apps/dev-playground/tests/data-visualization.spec.ts b/apps/dev-playground/tests/data-visualization.spec.ts index 07749bda..7c368148 100644 --- a/apps/dev-playground/tests/data-visualization.spec.ts +++ b/apps/dev-playground/tests/data-visualization.spec.ts @@ -4,7 +4,7 @@ import { setupMockAPI, trackApiCalls, waitForPageLoad, -} from "./fixtures/test-utils"; +} from "./utils/test-utils"; test.describe("Data Visualization Route Tests", () => { test.beforeEach(async ({ page }) => { diff --git a/apps/dev-playground/tests/navigation.spec.ts b/apps/dev-playground/tests/navigation.spec.ts deleted file mode 100644 index e761488a..00000000 --- a/apps/dev-playground/tests/navigation.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; - -const homepageNavigationTests: Array<{ - name: string; - buttonName: string; - buttonIndex?: number; - expectedUrl: string; -}> = [ - { - name: "analytics", - buttonName: "Explore real-time analytics", - buttonIndex: 0, - expectedUrl: "/analytics", - }, - { - name: "arrow-analytics", - buttonName: "Explore real-time analytics", - buttonIndex: 1, - expectedUrl: "/arrow-analytics", - }, - { - name: "reconnect", - buttonName: "View Reconnect Demo", - expectedUrl: "/reconnect", - }, - { - name: "data-visualization", - buttonName: "Explore data visualization", - expectedUrl: "/data-visualization", - }, - { - name: "telemetry", - buttonName: "Try Telemetry Examples", - expectedUrl: "/telemetry", - }, - { - name: "sql-helpers", - buttonName: "Try SQL Helpers", - expectedUrl: "/sql-helpers", - }, - { - name: "type-safety", - buttonName: "Explore Type Safety", - expectedUrl: "/type-safety", - }, -]; - -test.describe("Navigation Tests", () => { - test.beforeEach(async ({ page }) => { - await setupMockAPI(page); - }); - - for (const { - name, - buttonName, - buttonIndex, - expectedUrl, - } of homepageNavigationTests) { - test(`can navigate to ${name} from homepage`, async ({ page }) => { - await page.goto("/"); - await waitForPageLoad(page); - - const button = page.getByRole("button", { name: buttonName }); - if (buttonIndex !== undefined) { - await button.nth(buttonIndex).click(); - } else { - await button.click(); - } - - await expect(page).toHaveURL(expectedUrl); - }); - } - - test("navigation bar shows on non-home pages", async ({ page }) => { - await page.goto("/analytics"); - await waitForPageLoad(page); - - await expect( - page.getByRole("link", { name: "AppKit Playground" }), - ).toBeVisible(); - - await expect( - page.getByRole("button", { name: "Analytics", exact: true }), - ).toBeVisible(); - await expect( - page.getByRole("button", { name: "Arrow Analytics" }), - ).toBeVisible(); - await expect(page.getByRole("button", { name: "Reconnect" })).toBeVisible(); - await expect(page.getByRole("button", { name: "Telemetry" })).toBeVisible(); - await expect( - page.getByRole("button", { name: "SQL Helpers" }), - ).toBeVisible(); - }); - - test("can navigate back to home from nav bar", async ({ page }) => { - await page.goto("/analytics"); - await waitForPageLoad(page); - - await page.getByRole("link", { name: "AppKit Playground" }).click(); - - await expect(page).toHaveURL("/"); - }); - - test("can navigate between pages using nav bar", async ({ page }) => { - await page.goto("/analytics"); - await waitForPageLoad(page); - - await page.getByRole("button", { name: "Reconnect" }).click(); - await expect(page).toHaveURL("/reconnect"); - - await page.getByRole("button", { name: "Telemetry" }).click(); - await expect(page).toHaveURL("/telemetry"); - - await page.getByRole("button", { name: "SQL Helpers" }).click(); - await expect(page).toHaveURL("/sql-helpers"); - }); - - test("navigation bar is hidden on homepage", async ({ page }) => { - await page.goto("/"); - await waitForPageLoad(page); - - const navBar = page.locator("nav").filter({ - has: page.getByRole("button", { name: "Analytics" }), - }); - await expect(navBar).not.toBeVisible(); - }); -}); diff --git a/apps/dev-playground/tests/reconnect.spec.ts b/apps/dev-playground/tests/reconnect.spec.ts index 6d3a9a82..499a36ec 100644 --- a/apps/dev-playground/tests/reconnect.spec.ts +++ b/apps/dev-playground/tests/reconnect.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; +import { setupMockAPI, waitForPageLoad } from "./utils/test-utils"; test.describe("Reconnect Route Tests", () => { test.beforeEach(async ({ page }) => { diff --git a/apps/dev-playground/tests/smoke.spec.ts b/apps/dev-playground/tests/smoke.spec.ts index dcd9aa66..65c827fa 100644 --- a/apps/dev-playground/tests/smoke.spec.ts +++ b/apps/dev-playground/tests/smoke.spec.ts @@ -1,11 +1,7 @@ import { expect, test } from "@playwright/test"; -import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; +import { waitForPageLoad } from "./utils/test-utils"; test.describe("Smoke Tests", () => { - test.beforeEach(async ({ page }) => { - await setupMockAPI(page); - }); - test("app loads and displays homepage", async ({ page }) => { await page.goto("/"); await waitForPageLoad(page); @@ -19,37 +15,6 @@ test.describe("Smoke Tests", () => { ).toBeVisible(); }); - test("all feature cards are visible on homepage", async ({ page }) => { - await page.goto("/"); - await waitForPageLoad(page); - - await expect( - page.getByText("Analytics Dashboard", { exact: true }), - ).toBeVisible(); - await expect( - page.getByText("Arrow Analytics Dashboard", { exact: true }), - ).toBeVisible(); - await expect( - page.getByText("Stream Reconnection", { exact: true }), - ).toBeVisible(); - await expect( - page.getByText("Data Visualization", { exact: true }), - ).toBeVisible(); - await expect(page.getByText("Telemetry", { exact: true })).toBeVisible(); - await expect(page.getByText("SQL Helpers", { exact: true })).toBeVisible(); - await expect( - page.getByText("Type-Safe SQL", { exact: true }), - ).toBeVisible(); - }); - - test("theme selector is visible on homepage", async ({ page }) => { - await page.goto("/"); - await waitForPageLoad(page); - - const themeButton = page.locator('[class*="top-4"][class*="right-4"]'); - await expect(themeButton).toBeVisible(); - }); - test("no console errors on page load", async ({ page }) => { const consoleErrors: string[] = []; @@ -67,12 +32,4 @@ test.describe("Smoke Tests", () => { `Console errors detected: ${consoleErrors.join(", ")}`, ).toHaveLength(0); }); - - test("page has a title", async ({ page }) => { - await page.goto("/"); - await waitForPageLoad(page); - - const title = await page.title(); - expect(title).toBeTruthy(); - }); }); diff --git a/apps/dev-playground/tests/sql-helpers.spec.ts b/apps/dev-playground/tests/sql-helpers.spec.ts index 5e31b8cd..c7b63c46 100644 --- a/apps/dev-playground/tests/sql-helpers.spec.ts +++ b/apps/dev-playground/tests/sql-helpers.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; -import { mockAnalyticsData } from "./fixtures/mock-data"; -import { setupMockAPI, waitForPageLoad } from "./fixtures/test-utils"; +import { mockAnalyticsData } from "./utils/mock-data"; +import { setupMockAPI, waitForPageLoad } from "./utils/test-utils"; test.describe("SQL Helpers Route Tests", () => { test.beforeEach(async ({ page }) => { diff --git a/apps/dev-playground/tests/telemetry.spec.ts b/apps/dev-playground/tests/telemetry.spec.ts index d81b26ea..fb599cdd 100644 --- a/apps/dev-playground/tests/telemetry.spec.ts +++ b/apps/dev-playground/tests/telemetry.spec.ts @@ -3,7 +3,7 @@ import { setupMockAPI, trackApiCalls, waitForPageLoad, -} from "./fixtures/test-utils"; +} from "./utils/test-utils"; test.describe("Telemetry Route Tests", () => { test.beforeEach(async ({ page }) => { diff --git a/apps/dev-playground/tests/fixtures/mock-data.ts b/apps/dev-playground/tests/utils/mock-data.ts similarity index 100% rename from apps/dev-playground/tests/fixtures/mock-data.ts rename to apps/dev-playground/tests/utils/mock-data.ts diff --git a/apps/dev-playground/tests/fixtures/test-utils.ts b/apps/dev-playground/tests/utils/test-utils.ts similarity index 100% rename from apps/dev-playground/tests/fixtures/test-utils.ts rename to apps/dev-playground/tests/utils/test-utils.ts From 42893a897a1eb33008d28598f5112279d0a2b07a Mon Sep 17 00:00:00 2001 From: Jorge Calvar Date: Fri, 6 Feb 2026 11:48:33 +0100 Subject: [PATCH 3/6] refactor: simplify test navigation by using goto waitUntil option Signed-off-by: Jorge Calvar --- .../tests/arrow-analytics.spec.ts | 15 ++++------ .../tests/data-visualization.spec.ts | 28 ++++--------------- apps/dev-playground/tests/reconnect.spec.ts | 11 +++----- apps/dev-playground/tests/smoke.spec.ts | 7 ++--- apps/dev-playground/tests/sql-helpers.spec.ts | 21 ++------------ apps/dev-playground/tests/telemetry.spec.ts | 13 ++------- apps/dev-playground/tests/utils/test-utils.ts | 6 +--- 7 files changed, 23 insertions(+), 78 deletions(-) diff --git a/apps/dev-playground/tests/arrow-analytics.spec.ts b/apps/dev-playground/tests/arrow-analytics.spec.ts index 7b269d99..408c7517 100644 --- a/apps/dev-playground/tests/arrow-analytics.spec.ts +++ b/apps/dev-playground/tests/arrow-analytics.spec.ts @@ -4,7 +4,6 @@ import { setupMockAPI, trackApiCalls, waitForChartsToLoad, - waitForPageLoad, } from "./utils/test-utils"; test.describe("Arrow Analytics", () => { @@ -13,8 +12,8 @@ test.describe("Arrow Analytics", () => { }); test("page loads and displays heading", async ({ page }) => { - await page.goto("/arrow-analytics"); - await waitForPageLoad(page); + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); + await expect(page.getByText("Unified Charts API")).toBeVisible(); }); @@ -24,7 +23,7 @@ test.describe("Arrow Analytics", () => { const topContributorsCalls = trackApiCalls(page, "top_contributors"); const heatmapCalls = trackApiCalls(page, "app_activity_heatmap"); - await page.goto("/arrow-analytics"); + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await waitForChartsToLoad(page); @@ -35,7 +34,7 @@ test.describe("Arrow Analytics", () => { }); test("charts render with mock data (no empty states)", async ({ page }) => { - await page.goto("/arrow-analytics"); + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await waitForChartsToLoad(page); @@ -63,7 +62,7 @@ test.describe("Arrow Analytics", () => { test("chart tooltip appears on hover with mock app data", async ({ page, }) => { - await page.goto("/arrow-analytics"); + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); await waitForChartsToLoad(page); const barChart = page @@ -75,7 +74,6 @@ test.describe("Arrow Analytics", () => { const box = await barChart.boundingBox(); if (!box) throw new Error("Could not get chart bounding box"); - // Try multiple positions to find a data point (bar positions vary by chart size) const positions = [0.2, 0.35, 0.5, 0.65, 0.8]; for (const xRatio of positions) { await page.mouse.move( @@ -83,14 +81,13 @@ test.describe("Arrow Analytics", () => { box.y + box.height * 0.4, ); - // Check if tooltip appeared with any of our mock app names const tooltip = page .locator("div") .filter({ hasText: /App One|App Two|App Three/ }); try { await expect(tooltip.first()).toBeVisible({ timeout: 1000 }); - return; // Test passed - tooltip found + return; } catch {} } diff --git a/apps/dev-playground/tests/data-visualization.spec.ts b/apps/dev-playground/tests/data-visualization.spec.ts index 7c368148..55d1f4b6 100644 --- a/apps/dev-playground/tests/data-visualization.spec.ts +++ b/apps/dev-playground/tests/data-visualization.spec.ts @@ -3,7 +3,6 @@ import { STRICT_MODE_MULTIPLIER, setupMockAPI, trackApiCalls, - waitForPageLoad, } from "./utils/test-utils"; test.describe("Data Visualization Route Tests", () => { @@ -12,30 +11,25 @@ test.describe("Data Visualization Route Tests", () => { }); test("data-visualization page loads successfully", async ({ page }) => { - await page.goto("/data-visualization"); - await waitForPageLoad(page); + await page.goto("/data-visualization", { waitUntil: "networkidle" }); await expect(page).toHaveURL("/data-visualization"); }); test("page displays Data Visualization heading", async ({ page }) => { - await page.goto("/data-visualization"); - await waitForPageLoad(page); + await page.goto("/data-visualization", { waitUntil: "networkidle" }); await expect(page.getByText("Data Visualization")).toBeVisible(); }); test("simple data table displays mock data", async ({ page }) => { - await page.goto("/data-visualization"); - await waitForPageLoad(page); + await page.goto("/data-visualization", { waitUntil: "networkidle" }); - // The simple table is the first table on the page const simpleTable = page.locator("table").nth(0); await simpleTable.scrollIntoViewIfNeeded(); await expect(simpleTable).toBeVisible(); - // Verify the table contains expected mock data cells await expect( simpleTable.getByRole("cell", { name: "Untagged App 1" }), ).toBeVisible(); @@ -45,16 +39,13 @@ test.describe("Data Visualization Route Tests", () => { }); test("advanced data table displays mock data", async ({ page }) => { - await page.goto("/data-visualization"); - await waitForPageLoad(page); + await page.goto("/data-visualization", { waitUntil: "networkidle" }); - // The advanced table is the second table on the page const advancedTable = page.locator("table").nth(1); await advancedTable.scrollIntoViewIfNeeded(); await expect(advancedTable).toBeVisible(); - // Verify the table contains expected mock data cells await expect( advancedTable.getByRole("cell", { name: "Untagged App 2" }), ).toBeVisible(); @@ -68,31 +59,24 @@ test.describe("Data Visualization Route Tests", () => { const spendDataCalls = trackApiCalls(page, "spend_data"); const topContributorsCalls = trackApiCalls(page, "top_contributors"); - await page.goto("/data-visualization"); - await waitForPageLoad(page); + await page.goto("/data-visualization", { waitUntil: "networkidle" }); - // Scroll to load all charts and tables await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await page.waitForLoadState("networkidle"); - // Verify API calls: 2 tables use untagged_apps expect(untaggedAppsCalls.length).toBe(2 * STRICT_MODE_MULTIPLIER); - // Multiple charts use spend_data (AreaChart x2, LineChart x2, RadarChart x2) expect(spendDataCalls.length).toBe(6 * STRICT_MODE_MULTIPLIER); - // BarChart x2, PieChart x2 use top_contributors expect(topContributorsCalls.length).toBe(4 * STRICT_MODE_MULTIPLIER); }); test("can toggle code visibility", async ({ page }) => { - await page.goto("/data-visualization"); - await waitForPageLoad(page); + await page.goto("/data-visualization", { waitUntil: "networkidle" }); const showCodeButton = page .getByRole("button", { name: "Show Code" }) .first(); await showCodeButton.click(); - // Verify code section is revealed by checking the Hide Code button appears await expect( page.getByRole("button", { name: "Hide Code" }).first(), ).toBeVisible(); diff --git a/apps/dev-playground/tests/reconnect.spec.ts b/apps/dev-playground/tests/reconnect.spec.ts index 499a36ec..bdae60df 100644 --- a/apps/dev-playground/tests/reconnect.spec.ts +++ b/apps/dev-playground/tests/reconnect.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { setupMockAPI, waitForPageLoad } from "./utils/test-utils"; +import { setupMockAPI } from "./utils/test-utils"; test.describe("Reconnect Route Tests", () => { test.beforeEach(async ({ page }) => { @@ -7,8 +7,7 @@ test.describe("Reconnect Route Tests", () => { }); test("reconnect page loads successfully", async ({ page }) => { - await page.goto("/reconnect"); - await waitForPageLoad(page); + await page.goto("/reconnect", { waitUntil: "networkidle" }); await expect(page).toHaveURL("/reconnect"); }); @@ -18,9 +17,8 @@ test.describe("Reconnect Route Tests", () => { request.url().includes("/api/reconnect/stream"), ); - await page.goto("/reconnect"); + await page.goto("/reconnect", { waitUntil: "networkidle" }); await streamRequestPromise; - await waitForPageLoad(page); await expect( page.locator('[data-slot="badge"]').filter({ hasText: "Reconnected" }), @@ -35,8 +33,7 @@ test.describe("Reconnect Route Tests", () => { }); test("restart button triggers new stream connection", async ({ page }) => { - await page.goto("/reconnect"); - await waitForPageLoad(page); + await page.goto("/reconnect", { waitUntil: "networkidle" }); const newStreamRequestPromise = page.waitForRequest((request) => request.url().includes("/api/reconnect/stream"), diff --git a/apps/dev-playground/tests/smoke.spec.ts b/apps/dev-playground/tests/smoke.spec.ts index 65c827fa..e96a75eb 100644 --- a/apps/dev-playground/tests/smoke.spec.ts +++ b/apps/dev-playground/tests/smoke.spec.ts @@ -1,10 +1,8 @@ import { expect, test } from "@playwright/test"; -import { waitForPageLoad } from "./utils/test-utils"; test.describe("Smoke Tests", () => { test("app loads and displays homepage", async ({ page }) => { - await page.goto("/"); - await waitForPageLoad(page); + await page.goto("/", { waitUntil: "networkidle" }); await expect( page.getByRole("heading", { name: "AppKit Playground" }), @@ -24,8 +22,7 @@ test.describe("Smoke Tests", () => { } }); - await page.goto("/"); - await waitForPageLoad(page); + await page.goto("/", { waitUntil: "networkidle" }); expect( consoleErrors, diff --git a/apps/dev-playground/tests/sql-helpers.spec.ts b/apps/dev-playground/tests/sql-helpers.spec.ts index c7b63c46..7388d744 100644 --- a/apps/dev-playground/tests/sql-helpers.spec.ts +++ b/apps/dev-playground/tests/sql-helpers.spec.ts @@ -1,23 +1,18 @@ import { expect, test } from "@playwright/test"; import { mockAnalyticsData } from "./utils/mock-data"; -import { setupMockAPI, waitForPageLoad } from "./utils/test-utils"; +import { setupMockAPI } from "./utils/test-utils"; test.describe("SQL Helpers Route Tests", () => { test.beforeEach(async ({ page }) => { await setupMockAPI(page); + await page.goto("/sql-helpers", { waitUntil: "networkidle" }); }); test("sql-helpers page loads successfully", async ({ page }) => { - await page.goto("/sql-helpers"); - await waitForPageLoad(page); - await expect(page).toHaveURL("/sql-helpers"); }); test("can interact with string input", async ({ page }) => { - await page.goto("/sql-helpers"); - await waitForPageLoad(page); - const inputs = page.locator("input").first(); await inputs.fill("Test String Value"); @@ -25,9 +20,6 @@ test.describe("SQL Helpers Route Tests", () => { }); test("can interact with number input", async ({ page }) => { - await page.goto("/sql-helpers"); - await waitForPageLoad(page); - const numberInput = page.locator('input[type="number"]').first(); await numberInput.fill("123"); @@ -35,9 +27,6 @@ test.describe("SQL Helpers Route Tests", () => { }); test("can toggle boolean value", async ({ page }) => { - await page.goto("/sql-helpers"); - await waitForPageLoad(page); - const falseButton = page.getByRole("button", { name: "false" }); await falseButton.click(); @@ -46,9 +35,6 @@ test.describe("SQL Helpers Route Tests", () => { }); test("show code button reveals code example", async ({ page }) => { - await page.goto("/sql-helpers"); - await waitForPageLoad(page); - const showCodeButton = page .getByRole("button", { name: "Show Code" }) .first(); @@ -60,9 +46,6 @@ test.describe("SQL Helpers Route Tests", () => { test("sql_helpers_test query executes and displays mock data", async ({ page, }) => { - await page.goto("/sql-helpers"); - await waitForPageLoad(page); - await expect(page.getByText("Query executed successfully")).toBeVisible({ timeout: 5000, }); diff --git a/apps/dev-playground/tests/telemetry.spec.ts b/apps/dev-playground/tests/telemetry.spec.ts index fb599cdd..90bd91b3 100644 --- a/apps/dev-playground/tests/telemetry.spec.ts +++ b/apps/dev-playground/tests/telemetry.spec.ts @@ -1,19 +1,13 @@ import { expect, test } from "@playwright/test"; -import { - setupMockAPI, - trackApiCalls, - waitForPageLoad, -} from "./utils/test-utils"; +import { setupMockAPI, trackApiCalls } from "./utils/test-utils"; test.describe("Telemetry Route Tests", () => { test.beforeEach(async ({ page }) => { await setupMockAPI(page); + await page.goto("/telemetry", { waitUntil: "networkidle" }); }); test("telemetry page loads successfully", async ({ page }) => { - await page.goto("/telemetry"); - await waitForPageLoad(page); - await expect(page).toHaveURL("/telemetry"); }); @@ -22,9 +16,6 @@ test.describe("Telemetry Route Tests", () => { }) => { const requests = trackApiCalls(page, "/api/telemetry-examples"); - await page.goto("/telemetry"); - await waitForPageLoad(page); - const runButton = page.getByRole("button", { name: /Run.*Request/i }); await runButton.click(); diff --git a/apps/dev-playground/tests/utils/test-utils.ts b/apps/dev-playground/tests/utils/test-utils.ts index dbe6d280..a4449023 100644 --- a/apps/dev-playground/tests/utils/test-utils.ts +++ b/apps/dev-playground/tests/utils/test-utils.ts @@ -117,12 +117,8 @@ export async function setupMockAPI(page: Page) { }); } -export async function waitForPageLoad(page: Page) { - await page.waitForLoadState("networkidle"); -} - export async function waitForChartsToLoad(page: Page) { - await waitForPageLoad(page); + await page.waitForLoadState("networkidle"); await page.waitForFunction( () => document.querySelectorAll(".animate-pulse").length === 0, { timeout: 10000 }, From 3fd2b21dd1bdba3a4159b9e96e08ed07387e5664 Mon Sep 17 00:00:00 2001 From: Jorge Calvar Date: Fri, 6 Feb 2026 11:55:12 +0100 Subject: [PATCH 4/6] test: update playwright version --- apps/dev-playground/package.json | 2 +- pnpm-lock.yaml | 194 ++++++++++++++++++------------- 2 files changed, 111 insertions(+), 85 deletions(-) diff --git a/apps/dev-playground/package.json b/apps/dev-playground/package.json index 3b785e4f..c5781cf2 100644 --- a/apps/dev-playground/package.json +++ b/apps/dev-playground/package.json @@ -28,7 +28,7 @@ "zod": "^4.1.13" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.58.1", "@types/node": "^20.0.0", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^5.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6e02ed2..72bcd8ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,8 +137,8 @@ importers: version: 4.1.13 devDependencies: '@playwright/test': - specifier: ^1.50.1 - version: 1.58.0 + specifier: ^1.58.1 + version: 1.58.1 '@types/node': specifier: ^20.0.0 version: 20.19.21 @@ -1275,6 +1275,10 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -1978,9 +1982,15 @@ packages: '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -3006,8 +3016,8 @@ packages: resolution: {integrity: sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.110.0': - resolution: {integrity: sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow==} + '@oxc-project/types@0.112.0': + resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} '@oxc-project/types@0.93.0': resolution: {integrity: sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==} @@ -3020,8 +3030,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.58.0': - resolution: {integrity: sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==} + '@playwright/test@1.58.1': + resolution: {integrity: sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==} engines: {node: '>=18'} hasBin: true @@ -3732,8 +3742,8 @@ packages: cpu: [arm64] os: [android] - '@rolldown/binding-android-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA==} + '@rolldown/binding-android-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] @@ -3744,8 +3754,8 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] @@ -3756,8 +3766,8 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.1': - resolution: {integrity: sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ==} + '@rolldown/binding-darwin-x64@1.0.0-rc.3': + resolution: {integrity: sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] @@ -3768,8 +3778,8 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': - resolution: {integrity: sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': + resolution: {integrity: sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] @@ -3780,8 +3790,8 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': - resolution: {integrity: sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': + resolution: {integrity: sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -3792,8 +3802,8 @@ packages: cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3804,8 +3814,8 @@ packages: cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3816,8 +3826,8 @@ packages: cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -3828,8 +3838,8 @@ packages: cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -3840,8 +3850,8 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] @@ -3851,8 +3861,8 @@ packages: engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': - resolution: {integrity: sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': + resolution: {integrity: sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g==} engines: {node: '>=14.0.0'} cpu: [wasm32] @@ -3862,8 +3872,8 @@ packages: cpu: [arm64] os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] @@ -3880,8 +3890,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3895,8 +3905,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} - '@rolldown/pluginutils@1.0.0-rc.1': - resolution: {integrity: sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA==} + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} '@rollup/rollup-android-arm-eabi@4.52.4': resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} @@ -6850,15 +6860,17 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} @@ -8767,13 +8779,13 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - playwright-core@1.58.0: - resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==} + playwright-core@1.58.1: + resolution: {integrity: sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==} engines: {node: '>=18'} hasBin: true - playwright@1.58.0: - resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==} + playwright@1.58.1: + resolution: {integrity: sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==} engines: {node: '>=18'} hasBin: true @@ -9732,8 +9744,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.0.0-rc.1: - resolution: {integrity: sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg==} + rolldown@1.0.0-rc.3: + resolution: {integrity: sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -10923,6 +10935,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -12101,6 +12114,8 @@ snapshots: '@babel/runtime@7.28.4': {} + '@babel/runtime@7.28.6': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -13450,11 +13465,22 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.1.0': dependencies: tslib: 2.8.1 @@ -13922,8 +13948,8 @@ snapshots: '@napi-rs/wasm-runtime@1.1.1': dependencies: - '@emnapi/core': 1.7.1 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -14675,7 +14701,7 @@ snapshots: '@oxc-project/runtime@0.92.0': {} - '@oxc-project/types@0.110.0': {} + '@oxc-project/types@0.112.0': {} '@oxc-project/types@0.93.0': {} @@ -14684,9 +14710,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.58.0': + '@playwright/test@1.58.1': dependencies: - playwright: 1.58.0 + playwright: 1.58.1 '@pnpm/config.env-replace@1.1.0': {} @@ -15423,61 +15449,61 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-android-arm64@1.0.0-rc.1': + '@rolldown/binding-android-arm64@1.0.0-rc.3': optional: true '@rolldown/binding-darwin-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': optional: true '@rolldown/binding-darwin-x64@1.0.0-beta.41': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.1': + '@rolldown/binding-darwin-x64@1.0.0-rc.3': optional: true '@rolldown/binding-freebsd-x64@1.0.0-beta.41': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': optional: true '@rolldown/binding-linux-arm64-musl@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': optional: true '@rolldown/binding-linux-x64-gnu@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': optional: true '@rolldown/binding-linux-x64-musl@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': optional: true '@rolldown/binding-openharmony-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': optional: true '@rolldown/binding-wasm32-wasi@1.0.0-beta.41': @@ -15485,7 +15511,7 @@ snapshots: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true @@ -15493,7 +15519,7 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': optional: true '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41': @@ -15502,7 +15528,7 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.0.0-beta.41': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': optional: true '@rolldown/pluginutils@1.0.0-beta.38': {} @@ -15511,7 +15537,7 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.47': {} - '@rolldown/pluginutils@1.0.0-rc.1': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} '@rollup/rollup-android-arm-eabi@4.52.4': optional: true @@ -15642,7 +15668,7 @@ snapshots: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 invariant: 2.2.4 prop-types: 15.8.1 react: 19.2.0 @@ -21207,11 +21233,11 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 - playwright-core@1.58.0: {} + playwright-core@1.58.1: {} - playwright@1.58.0: + playwright@1.58.1: dependencies: - playwright-core: 1.58.0 + playwright-core: 1.58.1 optionalDependencies: fsevents: 2.3.2 @@ -22292,7 +22318,7 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-plugin-dts@0.16.11(rolldown@1.0.0-rc.1)(typescript@5.9.3): + rolldown-plugin-dts@0.16.11(rolldown@1.0.0-rc.3)(typescript@5.9.3): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.5 @@ -22303,7 +22329,7 @@ snapshots: dts-resolver: 2.1.2 get-tsconfig: 4.12.0 magic-string: 0.30.19 - rolldown: 1.0.0-rc.1 + rolldown: 1.0.0-rc.3 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -22367,24 +22393,24 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.41 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.41 - rolldown@1.0.0-rc.1: + rolldown@1.0.0-rc.3: dependencies: - '@oxc-project/types': 0.110.0 - '@rolldown/pluginutils': 1.0.0-rc.1 + '@oxc-project/types': 0.112.0 + '@rolldown/pluginutils': 1.0.0-rc.3 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.1 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.1 - '@rolldown/binding-darwin-x64': 1.0.0-rc.1 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.1 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.1 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.1 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.1 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.1 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.1 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.1 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.1 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.1 + '@rolldown/binding-android-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-x64': 1.0.0-rc.3 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.3 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.3 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3 rollup@4.52.4: dependencies: @@ -23034,8 +23060,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-rc.1 - rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-rc.1)(typescript@5.9.3) + rolldown: 1.0.0-rc.3 + rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-rc.3)(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.1 tinyglobby: 0.2.15 From 095c04853c6bafec39ea03159f2c3ed671a8733a Mon Sep 17 00:00:00 2001 From: Jorge Calvar <138497895+calvarjorge@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:22:21 +0100 Subject: [PATCH 5/6] ci: add playground integration tests job (#88) * ci: add playground integration tests job Signed-off-by: Jorge Calvar * chore: trigger CI Signed-off-by: Jorge Calvar * ci: skip ServiceContext initialization in CI Signed-off-by: Jorge Calvar * feat: allow passing a WorkspaceClient to createApp for e2e testing Signed-off-by: Jorge Calvar * chore: trigger CI Signed-off-by: Jorge Calvar * refactor: move e2e env vars from playwright config to CI workflow Signed-off-by: Jorge Calvar * docs: regenerate API docs for createApp client param Signed-off-by: Jorge Calvar --------- Signed-off-by: Jorge Calvar --- .github/workflows/ci.yml | 28 +++++++++++++++++++ apps/dev-playground/server/index.ts | 12 ++++++++ docs/docs/api/appkit/Function.createApp.md | 4 ++- .../appkit/src/context/service-context.ts | 23 +++++++++------ packages/appkit/src/core/appkit.ts | 5 +++- pnpm-lock.yaml | 6 ++-- 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29ecfc57..2a655d41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,34 @@ jobs: - name: Run Tests run: pnpm test + playground-integration-test: + name: Playground Integration Tests + needs: detect-changes + if: needs.detect-changes.outputs.appkit == 'true' + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Install Playwright Browsers + run: pnpm --filter=dev-playground exec playwright install --with-deps chromium + - name: Build packages + run: pnpm build + - name: Run Integration Tests + run: pnpm --filter=dev-playground test:integration + env: + APPKIT_E2E_TEST: 'true' + DATABRICKS_WAREHOUSE_ID: e2e-mock + DATABRICKS_WORKSPACE_ID: e2e-mock + docs-build: name: Docs Build needs: detect-changes diff --git a/apps/dev-playground/server/index.ts b/apps/dev-playground/server/index.ts index ed3fdaca..a56ba4a7 100644 --- a/apps/dev-playground/server/index.ts +++ b/apps/dev-playground/server/index.ts @@ -1,7 +1,18 @@ import { analytics, createApp, server } from "@databricks/appkit"; +import { WorkspaceClient } from "@databricks/sdk-experimental"; import { reconnect } from "./reconnect-plugin"; import { telemetryExamples } from "./telemetry-example-plugin"; +function createMockClient() { + const client = new WorkspaceClient({ + host: "http://localhost", + token: "e2e", + authType: "pat", + }); + client.currentUser.me = async () => ({ id: "e2e-test-user" }); + return client; +} + createApp({ plugins: [ server({ autoStart: false }), @@ -9,6 +20,7 @@ createApp({ telemetryExamples(), analytics({}), ], + ...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }), }).then((appkit) => { appkit.server .extend((app) => { diff --git a/docs/docs/api/appkit/Function.createApp.md b/docs/docs/api/appkit/Function.createApp.md index 35128e0c..85e7fa25 100644 --- a/docs/docs/api/appkit/Function.createApp.md +++ b/docs/docs/api/appkit/Function.createApp.md @@ -3,6 +3,7 @@ ```ts function createApp(config: { cache?: CacheConfig; + client?: WorkspaceClient; plugins?: T; telemetry?: TelemetryConfig; }): Promise>; @@ -20,8 +21,9 @@ Bootstraps AppKit with the provided configuration. | Parameter | Type | | ------ | ------ | -| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} | +| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `client?`: `WorkspaceClient`; `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} | | `config.cache?` | [`CacheConfig`](Interface.CacheConfig.md) | +| `config.client?` | `WorkspaceClient` | | `config.plugins?` | `T` | | `config.telemetry?` | [`TelemetryConfig`](Interface.TelemetryConfig.md) | diff --git a/packages/appkit/src/context/service-context.ts b/packages/appkit/src/context/service-context.ts index 0304196f..ee4b2453 100644 --- a/packages/appkit/src/context/service-context.ts +++ b/packages/appkit/src/context/service-context.ts @@ -57,8 +57,13 @@ export class ServiceContext { /** * Initialize the service context. Should be called once at app startup. * Safe to call multiple times - will return the same instance. + * + * @param client - Optional pre-configured WorkspaceClient to use instead + * of creating one from environment credentials. */ - static async initialize(): Promise { + static async initialize( + client?: WorkspaceClient, + ): Promise { if (ServiceContext.instance) { return ServiceContext.instance; } @@ -67,7 +72,7 @@ export class ServiceContext { return ServiceContext.initPromise; } - ServiceContext.initPromise = ServiceContext.createContext(); + ServiceContext.initPromise = ServiceContext.createContext(client); ServiceContext.instance = await ServiceContext.initPromise; return ServiceContext.instance; } @@ -147,19 +152,21 @@ export class ServiceContext { return getClientOptions(); } - private static async createContext(): Promise { - const client = new WorkspaceClient({}, getClientOptions()); + private static async createContext( + client?: WorkspaceClient, + ): Promise { + const wsClient = client ?? new WorkspaceClient({}, getClientOptions()); - const warehouseId = ServiceContext.getWarehouseId(client); - const workspaceId = ServiceContext.getWorkspaceId(client); - const currentUser = await client.currentUser.me(); + const warehouseId = ServiceContext.getWarehouseId(wsClient); + const workspaceId = ServiceContext.getWorkspaceId(wsClient); + const currentUser = await wsClient.currentUser.me(); if (!currentUser.id) { throw ConfigurationError.resourceNotFound("Service user ID"); } return { - client, + client: wsClient, serviceUserId: currentUser.id, warehouseId, workspaceId, diff --git a/packages/appkit/src/core/appkit.ts b/packages/appkit/src/core/appkit.ts index c34588e3..ed226b36 100644 --- a/packages/appkit/src/core/appkit.ts +++ b/packages/appkit/src/core/appkit.ts @@ -1,3 +1,4 @@ +import type { WorkspaceClient } from "@databricks/sdk-experimental"; import type { BasePlugin, CacheConfig, @@ -141,6 +142,7 @@ export class AppKit { plugins?: T; telemetry?: TelemetryConfig; cache?: CacheConfig; + client?: WorkspaceClient; } = {}, ): Promise> { // Initialize core services @@ -149,7 +151,7 @@ export class AppKit { // Initialize ServiceContext for Databricks client management // This provides the service principal client and shared resources - await ServiceContext.initialize(); + await ServiceContext.initialize(config?.client); const rawPlugins = config.plugins as T; const preparedPlugins = AppKit.preparePlugins(rawPlugins); @@ -188,6 +190,7 @@ export async function createApp< plugins?: T; telemetry?: TelemetryConfig; cache?: CacheConfig; + client?: WorkspaceClient; } = {}, ): Promise> { return AppKit._createApp(config); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72bcd8ec..587a1d24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18088,7 +18088,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 csstype: 3.1.3 dom-serializer@1.4.1: @@ -19268,7 +19268,7 @@ snapshots: history@4.10.1: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.3 @@ -21986,7 +21986,7 @@ snapshots: react-transition-group@4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 From c29611f433d85c10fff6acf949874d6c01baa017 Mon Sep 17 00:00:00 2001 From: Jorge Calvar Date: Mon, 9 Feb 2026 13:38:46 +0100 Subject: [PATCH 6/6] chore: update CONTRIBUTING.md and playwright config to use DATABRICKS_APP_PORT --- CONTRIBUTING.md | 7 +++++++ apps/dev-playground/playwright.config.ts | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea17a664..73bcbe76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,13 @@ The following command will compile all the packages and app in watch mode. pnpm dev ``` +> **Note:** To avoid port collisions with the `clean-app` example, you should create a `.env` file in `apps/dev-playground` and set another port for this app: +> +> ``` +> DATABRICKS_APP_PORT=8001 +> ``` + + ## Running the project in production mode Running the following command diff --git a/apps/dev-playground/playwright.config.ts b/apps/dev-playground/playwright.config.ts index b0ca6f92..8a2546f7 100644 --- a/apps/dev-playground/playwright.config.ts +++ b/apps/dev-playground/playwright.config.ts @@ -1,4 +1,5 @@ import { defineConfig, devices } from "@playwright/test"; +import "dotenv/config"; export default defineConfig({ testDir: "./tests", @@ -8,7 +9,7 @@ export default defineConfig({ workers: process.env.CI ? 1 : undefined, reporter: "html", use: { - baseURL: "http://localhost:8000", + baseURL: `http://localhost:${process.env.DATABRICKS_APP_PORT || 8000}`, trace: "on-first-retry", }, projects: [ @@ -19,7 +20,7 @@ export default defineConfig({ ], webServer: { command: "pnpm dev", - url: "http://localhost:8000", + url: `http://localhost:${process.env.DATABRICKS_APP_PORT || 8000}`, reuseExistingServer: !process.env.CI, timeout: 120 * 1000, },