diff --git a/.github/workflows/studio-e2e-tests.yml b/.github/workflows/studio-e2e-tests.yml index 54bbb01d07..1a57cd0b0d 100644 --- a/.github/workflows/studio-e2e-tests.yml +++ b/.github/workflows/studio-e2e-tests.yml @@ -5,14 +5,14 @@ on: paths: - 'packages/pg-meta/**/*' - 'apps/studio/**' - - 'tests/studio-tests/**' + - 'e2e/studio/**' - 'pnpm-lock.yaml' pull_request: branches: [master] paths: - 'packages/pg-meta/**/*' - 'apps/studio/**' - - 'tests/studio-tests/**' + - 'e2e/studio/**' - 'pnpm-lock.yaml' # Cancel old builds on new commit for same workflow + branch/PR @@ -27,6 +27,18 @@ jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest + # Make the job non-blocking + continue-on-error: true + + env: + EMAIL: ${{ secrets.CI_EMAIL }} + PASSWORD: ${{ secrets.CI_PASSWORD }} + PROJECT_REF: ${{ secrets.CI_PROJECT_REF }} + NEXT_PUBLIC_IS_PLATFORM: true + NEXT_PUBLIC_API_URL: https://api.supabase.green + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_STUDIO_HOSTED_PROJECT_ID }} + NEXT_PUBLIC_HCAPTCHA_SITE_KEY: 10000000-ffff-ffff-ffff-000000000001 steps: - uses: actions/checkout@v4 @@ -39,20 +51,41 @@ jobs: with: node-version-file: '.nvmrc' cache: 'pnpm' - - uses: supabase/setup-cli@v1 - with: - version: latest + - name: Install dependencies run: pnpm i + + - name: Install Vercel CLI + run: pnpm add --global vercel@latest + + - name: Pull Vercel Environment Information (Preview) + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + + - name: Build Project Artifacts for Vercel + run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + + - name: Deploy Project to Vercel and Get URL + id: deploy_vercel + run: | + DEPLOY_URL=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}) + echo "Vercel Preview URL: $DEPLOY_URL" + echo "DEPLOY_URL=$DEPLOY_URL" >> $GITHUB_OUTPUT + - name: Install Playwright Browsers - run: pnpm -C tests/studio-tests exec playwright install --with-deps + run: pnpm -C e2e/studio exec playwright install --with-deps + - name: Run Playwright tests - run: pnpm test:e2e:studio-local - # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable. - # continue-on-error: true + id: playwright + env: + AUTHENTICATION: true + STUDIO_URL: ${{ steps.deploy_vercel.outputs.DEPLOY_URL }}/dashboard + run: pnpm e2e + - uses: actions/upload-artifact@v4 if: always() with: - name: playwright-report - path: tests/studio-tests/playwright-report/ + name: playwright-artifacts + path: | + e2e/studio/playwright-report/ + e2e/studio/test-results/ retention-days: 7 diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/RunButton.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/RunButton.tsx index 03faf85ac5..627e73768a 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/RunButton.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/RunButton.tsx @@ -29,6 +29,7 @@ export const SqlRunButton = ({ disabled={isDisabled} type="primary" size="tiny" + data-testid="sql-run-button" iconRight={ isExecuting ? ( diff --git a/apps/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx b/apps/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx index cfcd76b665..5da4f516be 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx @@ -184,12 +184,7 @@ const DefaultPreviewSelectionRenderer = ({ log }: { log: PreviewLogData }) => { )} {log?.status && } {log?.timestamp && ( - + )} {Object.entries(rest).map(([key, value]) => { return diff --git a/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx b/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx index 5c9c0d7ece..7296bb61f6 100644 --- a/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx +++ b/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx @@ -23,13 +23,15 @@ const LogsLayout = ({ title, children }: PropsWithChildren) => const router = useRouter() const [_, setLastLogsPage] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.LAST_VISITED_LOGS_PAGE, - router.pathname.split('/logs/')[1] + router.pathname.split('/logs/')[1] || '' ) useEffect(() => { if (router.pathname.includes('/logs/')) { const path = router.pathname.split('/logs/')[1] - setLastLogsPage(path) + if (path) { + setLastLogsPage(path) + } } }, [router, setLastLogsPage]) diff --git a/apps/studio/tests/README.md b/apps/studio/tests/README.md index 28ea24fa06..605f7cab87 100644 --- a/apps/studio/tests/README.md +++ b/apps/studio/tests/README.md @@ -60,6 +60,7 @@ test('mock is working', async () => { To render a component that uses Nuqs with some predefined query parameters, you can use `customRender` with the `nuqs` prop. ```ts + customRender(, { nuqs: { searchParams: { diff --git a/e2e/studio/.env.local.example b/e2e/studio/.env.local.example new file mode 100644 index 0000000000..256a6101fb --- /dev/null +++ b/e2e/studio/.env.local.example @@ -0,0 +1,14 @@ + +# 1. Copy and paste this file and rename it to .env.local + +# 2. Set the STUDIO_URL and API_URL you want the e2e tests to run against + +STUDIO_URL=https://supabase.com/dashboard +API_URL=https://api.supabase.com +AUTHENTICATION=true + +# 3. *Optional* If the environment requires auth, set AUTHENTICATION to true, auth credentials, and PROJECT_REF + +EMAIL= +PASSWORD= +PROJECT_REF= diff --git a/tests/studio-tests/.gitignore b/e2e/studio/.gitignore similarity index 100% rename from tests/studio-tests/.gitignore rename to e2e/studio/.gitignore diff --git a/e2e/studio/README.md b/e2e/studio/README.md new file mode 100644 index 0000000000..21e66a97f7 --- /dev/null +++ b/e2e/studio/README.md @@ -0,0 +1,143 @@ +# Supabase Studio E2E Tests + +## Set up + +```bash +cp .env.local.example .env.local +``` + +Edit the `.env.local` file with your credentials and environment. + +### Install the playwright browser + +```bash +pnpm exec playwright install +``` + +## Environments + +### Staging + +```bash +STUDIO_URL=https://supabase.green/dashboard +API_URL=https://api.supabase.green +AUTHENTICATION=true +EMAIL=your@email.com +PASSWORD=yourpassword +PROJECT_REF=yourprojectref +``` + +### CLI (NO AUTH) + +You'll need to run the CLI locally. + +```bash +STUDIO_URL=http://localhost:54323 +API_URL=http://localhost:54323/api +AUTHENTICATION=false +``` + +### CLI Development (NO AUTH) + +You'll need to run Studio in development mode with `IS_PLATFORM=false` + +```bash +STUDIO_URL=http://localhost:8082/ +API_URL=http://localhost:8082/api +AUTHENTICATION=false +``` + +### Hosted Development + +You'll need to run Studio in development mode with `IS_PLATFORM=true` + +```bash +STUDIO_URL=http://localhost:8082/ +API_URL=http://localhost:8080/api +AUTHENTICATION=true +EMAIL=your@email.com +PASSWORD=yourpassword +PROJECT_REF=yourprojectref +``` + +--- + +## Running the tests + +Check the `package.json` for the available commands and environments. + +#### Example: + +```bash +pnpm run e2e +``` + +With Playwright UI: + +```bash +pnpm run e2e -- --ui +``` + +--- + +## Tips for development + +- Read [Playwright Best Practices](https://playwright.dev/docs/best-practices) +- Use `pnpm run e2e -- --ui` to get the playwright UI. +- Add the tests in `examples/examples.ts` to Cursor as context. +- Add messages to expect statements to make them easier to debug. + +Example: + +```ts +await expect(page.getByRole('heading', { name: 'Logs & Analytics' }), { + message: 'Logs heading should be visible', +}).toBeVisible() +``` + +- Use the test utility instead of playwrights test. + +```ts +import { test } from '../utils/test' +``` + +- Use the PWDEBUG environment variable to debug the tests. + +```bash +PWDEBUG=1 pnpm run e2e -- --ui +``` + +--- + +## Organization + +Name the folders based on the feature you are testing. + +```bash +e2e/studio/logs/ +e2e/studio/sql-editor/ +e2e/studio/storage/ +e2e/studio/auth/ +``` + +--- + +## What should I test? + +- Can the feature be navigated to? +- Does the feature load correctly? +- Can you do the actions (filtering, sorting, opening dialogs, etc)? + +--- + +## API Mocks + +Read here: https://playwright.dev/docs/mock#mock-api-requests + +Example: + +```ts +await page.route(`*/**/logs.all*`, async (route) => { + await route.fulfill({ body: JSON.stringify(mockAPILogs) }) +}) +``` diff --git a/e2e/studio/env.config.ts b/e2e/studio/env.config.ts new file mode 100644 index 0000000000..4629685171 --- /dev/null +++ b/e2e/studio/env.config.ts @@ -0,0 +1,12 @@ +import path from 'path' + +export const env = { + STUDIO_URL: process.env.STUDIO_URL, + API_URL: process.env.API_URL || 'https://api.supabase.green', + AUTHENTICATION: process.env.AUTHENTICATION, + EMAIL: process.env.EMAIL, + PASSWORD: process.env.PASSWORD, + PROJECT_REF: process.env.PROJECT_REF || 'default', +} + +export const STORAGE_STATE_PATH = path.join(__dirname, './playwright/.auth/user.json') diff --git a/e2e/studio/examples/examples.ts b/e2e/studio/examples/examples.ts new file mode 100644 index 0000000000..f9cd5f6ee0 --- /dev/null +++ b/e2e/studio/examples/examples.ts @@ -0,0 +1,79 @@ +import { expect } from '@playwright/test' +import { isEnv } from '../env.config' +import { test } from '../utils/test' + +/** + * * Example tests for Studio. + * Tips: + * - Use the test utility instead of playwrights test. + * import { test } from '../utils/test' + * - Use the isEnv utility to check the environment. + * import { isEnv } from '../env.config' + * - Make tests easy to debug by adding enough expect() statements. + */ + +/** + * * Test that is skipped in self-hosted environment + */ +test('Loads the page 1', async ({ page }) => { + if (isEnv('selfhosted')) return + + await page.goto('https://www.supabase.com') + await expect( + page.getByRole('heading', { name: 'Build in a weekend Scale to millions' }) + ).toBeVisible() +}) + +/** + * * Test that only runs in staging and production environments + */ +test('Loads the page 2', async ({ page }) => { + if (!isEnv(['staging', 'production'])) return + + await page.goto('https://www.supabase.com') + await expect( + page.getByRole('heading', { name: 'Build in a weekend Scale to millions' }) + ).toBeVisible() +}) + +/** + * * Test that navigates to a project by ref + * Make sure to set up the project in the `.env.local` file. + */ +test('Navigates to a project by ref', async ({ page, ref }) => { + await page.goto(`${process.env.BASE_URL}/project/${ref}`) + await expect(page.getByRole('heading', { name: 'Project Home' })).toBeVisible() +}) + +/** + * * Test that mocks some API calls + */ + +const mockRes = { + data: [ + { + id: 1, + name: 'John Doe', + email: 'john.doe@example.com', + }, + { + id: 2, + name: 'Jane Doe', + email: 'jane.doe@example.com', + }, + ], +} + +test.beforeEach(async ({ context }) => { + context.route('*/**/users*', async (route, request) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(mockRes), + }) + }) +}) + +test('Mocks some API calls', async ({ page }) => { + // ... Run some code that depends on that API call +}) diff --git a/e2e/studio/features/_global.setup.ts b/e2e/studio/features/_global.setup.ts new file mode 100644 index 0000000000..187e72fb8f --- /dev/null +++ b/e2e/studio/features/_global.setup.ts @@ -0,0 +1,155 @@ +import { expect, test as setup } from '@playwright/test' +import dotenv from 'dotenv' +import path from 'path' +import { env, STORAGE_STATE_PATH } from '../env.config' + +/** + * Run any setup tasks for the tests. + * Catch errors and show useful messages. + */ + +dotenv.config({ + path: path.resolve(__dirname, '..', '.env.local'), + override: true, +}) + +const IS_PLATFORM = process.env.IS_PLATFORM + +const envHasAuth = env.AUTHENTICATION + +setup('Global Setup', async ({ page }) => { + console.log(`\n ๐Ÿงช Setting up test environment. + - Studio URL: ${env.STUDIO_URL} + - API URL: ${env.API_URL} + - Auth: ${envHasAuth ? 'enabled' : 'disabled'} + - Is Platform: ${IS_PLATFORM} + `) + + /** + * Studio Check + */ + + const studioUrl = env.STUDIO_URL + const apiUrl = env.API_URL + + await page.goto(studioUrl).catch((err) => { + console.error( + `\n ๐Ÿšจ Setup Error +Studio is not available at: ${studioUrl} + +Please ensure: + 1. Studio is running in the expected URL + 2. You have proper network access +` + ) + throw err + }) + + console.log(`\n โœ… Studio is running at ${studioUrl}`) + + /** + * API Check + */ + + await fetch(apiUrl).catch((err) => { + console.error(`\n ๐Ÿšจ Setup Error +API is not available at: ${apiUrl} + +Please ensure: + 1. API is running in the expected URL + 2. You have proper network access + +To start API locally, run: + npm run dev:api`) + throw new Error('API is not available') + }) + + console.log(`\n โœ… API is running at ${apiUrl}`) + + /** + * Only run authentication if the environment requires it + */ + if (!env.AUTHENTICATION) { + console.log(`\n ๐Ÿ”‘ Skipping authentication for ${env.STUDIO_URL}`) + return + } else { + if (!env.EMAIL || !env.PASSWORD || !env.PROJECT_REF) { + console.error(`Missing environment variables. Check README.md for more information.`) + throw new Error('Missing environment variables') + } + } + + const signInUrl = `${studioUrl}/sign-in` + console.log(`\n ๐Ÿ”‘ Navigating to sign in page: ${signInUrl}`) + + await page.goto(signInUrl, { waitUntil: 'networkidle' }) + await page.waitForLoadState('domcontentloaded') + await page.waitForLoadState('networkidle') + + // Check if we're still on the sign-in page + const currentUrl = page.url() + console.log(`\n ๐Ÿ“ Current URL: ${currentUrl}`) + + if (!currentUrl.includes('/sign-in')) { + console.log('\n โš ๏ธ Redirected away from sign-in page. Checking if already authenticated...') + + // Check if we're already on the projects page + if (currentUrl.includes('/projects')) { + console.log('\n โœ… Already authenticated, proceeding with tests') + await page.context().storageState({ path: STORAGE_STATE_PATH }) + return + } + + // If we're redirected somewhere else, try to navigate back to sign-in + console.log('\n ๐Ÿ”„ Attempting to navigate back to sign-in page') + await page.goto(signInUrl, { waitUntil: 'networkidle' }) + await page.waitForLoadState('domcontentloaded') + await page.waitForLoadState('networkidle') + + // Check URL again after second attempt + const secondAttemptUrl = page.url() + if (!secondAttemptUrl.includes('/sign-in')) { + throw new Error(`Failed to reach sign-in page. Current URL: ${secondAttemptUrl}`) + } + } + + const auth = { + email: env.EMAIL, + password: env.PASSWORD, + projectRef: env.PROJECT_REF, + } + + expect(auth).toBeDefined() + expect(auth.email).toBeDefined() + expect(auth.password).toBeDefined() + expect(auth.projectRef).toBeDefined() + + // Wait for form elements with increased timeout + const emailInput = page.getByLabel('Email') + const passwordInput = page.getByLabel('Password') + const signInButton = page.getByRole('button', { name: 'Sign In' }) + + // if found click opt out on telemetry + const optOutButton = page.getByRole('button', { name: 'Opt out' }) + if ((await optOutButton.count()) > 0) { + await optOutButton.click() + } + + // Debug element states + console.log('\n ๐Ÿ” Checking form elements:') + console.log(`Email input exists: ${(await emailInput.count()) > 0}`) + console.log(`Password input exists: ${(await passwordInput.count()) > 0}`) + console.log(`Sign in button exists: ${(await signInButton.count()) > 0}`) + + await emailInput.waitFor({ state: 'visible', timeout: 15000 }) + await passwordInput.waitFor({ state: 'visible', timeout: 15000 }) + await signInButton.waitFor({ state: 'visible', timeout: 15000 }) + + await emailInput.fill(auth.email ?? '') + await passwordInput.fill(auth.password ?? '') + await signInButton.click() + + await page.waitForURL('**/organizations') + + await page.context().storageState({ path: STORAGE_STATE_PATH }) +}) diff --git a/e2e/studio/features/home.spec.ts b/e2e/studio/features/home.spec.ts new file mode 100644 index 0000000000..38a0adbbed --- /dev/null +++ b/e2e/studio/features/home.spec.ts @@ -0,0 +1,12 @@ +import { expect } from '@playwright/test' +import { test } from '../utils/test' +import { toUrl } from '../utils/to-url' + +test.describe('Project', async () => { + test('Can navigate to project home page', async ({ page, ref }) => { + console.log(page.url()) + await page.goto(toUrl(`/project/${ref}`)) + + await expect(page.getByRole('button', { name: 'Project Status' })).toBeVisible() + }) +}) diff --git a/e2e/studio/features/logs.spec.ts b/e2e/studio/features/logs.spec.ts new file mode 100644 index 0000000000..87cc79c188 --- /dev/null +++ b/e2e/studio/features/logs.spec.ts @@ -0,0 +1,90 @@ +import { expect } from '@playwright/test' +import { test } from '../utils/test' +import { toUrl } from '../utils/to-url' +const LOGS_PAGES = [ + { label: 'API Gateway', route: 'edge-logs' }, + { label: 'Postgres', route: 'postgres-logs' }, +] + +const mockAPILogs = { + error: null, + result: [ + { + id: 'uuid-1', + timestamp: 1713200000000, // 15 Apr 18:53:20" + event_message: 'Random event message: uuid-1', + count: 123, + ok_count: 123, + error_count: 0, + warning_count: 20, + metadata: { + foo: 'bar', + request: { + url: 'https://example.com', + }, + response: { + status: 200, + }, + }, + }, + ], +} + +test.beforeEach(async ({ context }) => { + context.route(/.*logs\.all.*/, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(mockAPILogs), + }) + }) +}) + +test.describe('Logs', () => { + for (const logPage of LOGS_PAGES) { + test(`${logPage.label} logs page`, async ({ page, ref }) => { + /** + * Navigates to Logs + */ + await page.goto(toUrl(`/project/${ref}/logs/${logPage.route}`)) + + await expect(page.getByRole('heading', { name: 'Logs & Analytics' }), { + message: 'Logs & Analytics heading should be visible', + }).toBeVisible() + + /** + * Shows the logs table + */ + + const logsTable = page.getByRole('table') + + await expect(logsTable, { + message: 'Logs table should be visible', + }).toBeVisible({ timeout: 20000 }) + + /** + * Shows the logs data without errors + */ + await expect(page.getByText(mockAPILogs.result[0].event_message), { + message: 'Logs data should be visible', + }).toBeVisible() + + /** + * Can select and view log details + */ + const gridcells = page.getByText('Random event message') + await gridcells.click() + + const tabPanel = page.getByTestId('log-selection') + await expect(tabPanel).toBeVisible() + + // Assert known fixed values instead of extracting text + await expect(tabPanel, { + message: 'Log selection should be visible', + }).toContainText('Random event message: uuid-1') + await expect(tabPanel.getByTestId('log-selection-id'), { + message: 'Log selection ID should be visible', + }).toContainText('uuid-1') + }) + } +}) diff --git a/e2e/studio/features/sql-editor.spec.ts b/e2e/studio/features/sql-editor.spec.ts new file mode 100644 index 0000000000..efb523b418 --- /dev/null +++ b/e2e/studio/features/sql-editor.spec.ts @@ -0,0 +1,36 @@ +import { expect } from '@playwright/test' +import { test } from '../utils/test' +import { toUrl } from '../utils/to-url' +import { env } from '../env.config' + +test.describe('SQL Editor', () => { + test('should check if SQL editor can run simple commands', async ({ page }) => { + await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql`)) + + const editor = page.getByRole('code').nth(0) + + // write some sql in the editor + // This has to be done since the editor is not editable (input, textarea, etc.) + await editor.click() + await editor.click() + await page.keyboard.press('ControlOrMeta+KeyA') + await page.keyboard.type(`select 'hello world';`) + + await page.getByRole('button', { name: /^Run( CTRL)?$/, exact: false }).click() + + // Should say "Running..." + await expect(page.getByText('Running...')).toBeVisible() + + // Wait until Running... is not visible + await expect(page.getByText('Running...')).not.toBeVisible() + + // clear the editor + await editor.click() + await page.keyboard.press('ControlOrMeta+KeyA') + await page.keyboard.press('Backspace') + + // verify the result + const result = page.getByRole('gridcell', { name: 'hello world' }) + await expect(result).toBeVisible() + }) +}) diff --git a/e2e/studio/features/table-editor.spec.ts b/e2e/studio/features/table-editor.spec.ts new file mode 100644 index 0000000000..c23238a665 --- /dev/null +++ b/e2e/studio/features/table-editor.spec.ts @@ -0,0 +1,210 @@ +import { expect, Page } from '@playwright/test' +import { test } from '../utils/test' +import { toUrl } from '../utils/to-url' + +// Helper to generate a random table name +const getRandomTableName = () => `pw-test-${Math.floor(Math.random() * 10000)}` + +const getSelectors = (tableName: string) => ({ + tableButton: (page) => page.getByRole('button', { name: `View ${tableName}` }), + newTableBtn: (page) => page.getByRole('button', { name: 'New table', exact: true }), + tableNameInput: (page) => page.getByTestId('table-name-input'), + createdAtExtraOptions: (page) => page.getByTestId('created_at-extra-options'), + addColumnBtn: (page) => page.getByRole('button', { name: 'Add column' }), + columnNameInput: (page) => page.getByRole('textbox', { name: 'column_name' }), + chooseColumnType: (page) => page.locator('button').filter({ hasText: 'Choose a column type...' }), + signedIntOption: (page) => page.getByText('Signed two-byte integer'), + defaultValueField: (page) => page.getByTestId('defaultValueColumn-default-value'), + saveBtn: (page) => page.getByRole('button', { name: 'Save' }), + definitionTab: (page) => page.getByText('definition', { exact: true }), + viewLines: (page) => page.locator('div.view-lines'), + insertRowBtn: (page) => page.getByTestId('table-editor-insert-new-row'), + insertModal: (page) => page.getByText('Insert a new row into'), + defaultValueInput: (page) => page.getByTestId('defaultValueColumn-input'), + actionBarSaveRow: (page) => page.getByTestId('action-bar-save-row'), + grid: (page) => page.getByRole('grid'), + row: (page) => page.getByRole('row'), + sortBtn: (page) => page.getByRole('button', { name: 'Sort', exact: true }), + pickSortColumnBtn: (page) => page.getByTestId('table-editor-pick-column-to-sort-button'), + sortColumnOption: (page) => + page.getByLabel('Pick a column to sort by').getByText('defaultValueColumn'), + applySortingBtn: (page) => page.getByRole('button', { name: 'Apply sorting' }), + sortedByRuleBtn: (page) => page.getByRole('button', { name: 'Sorted by 1 rule' }), + filterBtn: (page) => page.getByRole('button', { name: 'Filter', exact: true }), + addFilterBtn: (page) => page.getByRole('button', { name: 'Add filter' }), + columnPickerBtn: (page) => page.getByRole('button', { name: 'id' }), + filterColumnOption: (page) => page.getByLabel('id').getByText('defaultValueColumn'), + filterInput: (page) => page.getByPlaceholder('Enter a value'), + applyFilterBtn: (page) => page.getByRole('button', { name: 'Apply filter' }), + viewTableLabel: (page) => page.getByLabel(`View ${tableName}`, { exact: true }), + deleteTableBtn: (page) => page.getByText('Delete table'), + confirmDeleteBtn: (page) => page.getByRole('button', { name: 'Delete' }), + rlsCheckbox: (page) => page.getByLabel('Enable Row Level Security ('), + rlsConfirmBtn: (page) => page.getByRole('button', { name: 'Confirm' }), + deleteTableToast: (page) => page.getByText('Successfully deleted table "'), +}) + +test.describe('Table Editor', () => { + let page: Page + let tableName: string + + test.beforeAll(async ({ browser, ref }) => { + test.setTimeout(60000) + + /** + * Create a new table for the tests + */ + page = await browser.newPage() + + await page.goto(toUrl(`/project/${ref}/editor`)) + + tableName = getRandomTableName() + const s = getSelectors(tableName) + + await s.newTableBtn(page).click() + await s.tableNameInput(page).fill(tableName) + + await s.createdAtExtraOptions(page).click() + await page.getByText('Is Nullable').click() + await s.createdAtExtraOptions(page).click({ force: true }) + + await s.addColumnBtn(page).click() + await s.columnNameInput(page).fill('defaultValueColumn') + await s.chooseColumnType(page).click() + await s.signedIntOption(page).click() + await s.defaultValueField(page).click() + await s.defaultValueField(page).fill('2') + + await s.saveBtn(page).click() + + // wait till we see the success toast + // Text: Table tableName is good to go! + + await expect( + page.getByText(`Table ${tableName} is good to go!`), + 'Success toast should be visible after table creation' + ).toBeVisible({ + timeout: 50000, + }) + + await expect( + page.getByRole('button', { name: `View ${tableName}` }), + 'Table should be visible after creation' + ).toBeVisible() + }) + + test.afterAll(async () => { + test.setTimeout(60000) + /** + * Delete the table after the tests are done + */ + const s = getSelectors(tableName) + + const exists = (await s.tableButton(page).count()) > 0 + if (!exists) return + + await s.viewTableLabel(page).click() + await s.viewTableLabel(page).getByRole('button').nth(1).click() + await s.deleteTableBtn(page).click() + await s.confirmDeleteBtn(page).click() + await expect( + s.deleteTableToast(page), + 'Delete confirmation toast should be visible' + ).toBeVisible() + }) + + test('should perform all table operations sequentially', async ({ ref }) => { + const s = getSelectors(tableName) + test.setTimeout(60000) + + // 1. View table definition + await page.evaluate(() => document.querySelector('.ReactQueryDevtools')?.remove()) + await s.definitionTab(page).click() + await expect( + s.viewLines(page), + 'Table definition should contain the correct SQL' + ).toContainText( + `CREATE TABLE public.${tableName} ( id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, created_at timestamp with time zone NULL DEFAULT now(), "defaultValueColumn" smallint NULL DEFAULT '2'::smallint, CONSTRAINT ${tableName}_pkey PRIMARY KEY (id)) TABLESPACE pg_default;` + ) + + // 2. Insert test data + await page.getByRole('button', { name: `View ${tableName}` }).click() + await s.insertRowBtn(page).click() + await s.insertModal(page).click() + await s.defaultValueInput(page).fill('100') + await s.actionBarSaveRow(page).click() + + await page.getByRole('button', { name: `View ${tableName}` }).click() + await s.insertRowBtn(page).click() + await s.insertModal(page).click() + await s.defaultValueInput(page).fill('4') + await s.actionBarSaveRow(page).click() + + // Wait for the grid to be visible and data to be loaded + await expect(s.grid(page), 'Grid should be visible after inserting data').toBeVisible() + + // 3. Sort rows + await s.sortBtn(page).click() + await s.pickSortColumnBtn(page).click() + await s.sortColumnOption(page).click() + await s.applySortingBtn(page).click() + await page.keyboard.down('Escape') + + // Wait for sorting to complete + await page.waitForResponse((response) => response.url().includes(`pg-meta/${ref}/query`)) + + // give it a second to rerender + await page.waitForTimeout(1000) + + const defaultValueCells = page.getByRole('gridcell') + + const thirdGridCell = defaultValueCells.nth(3) + const thirdGridCellText = await thirdGridCell.textContent() + expect(thirdGridCellText, 'Third grid cell should contain the value "4"').toEqual('4') + + // 4. Filter rows + await s.filterBtn(page).click() + await s.addFilterBtn(page).click() + await s.columnPickerBtn(page).click() + await s.filterColumnOption(page).click() + await s.filterInput(page).fill('4') + await s.applyFilterBtn(page).click() + await page.keyboard.down('Escape') + + await expect( + s.grid(page).getByRole('gridcell', { name: '4', exact: true }), + 'Filtered value "4" should be visible' + ).toBeVisible() + await expect( + s.grid(page).getByText('100'), + 'Filtered value "100" should not be visible' + ).not.toBeVisible() + + // 5. Check auth schema + await page.getByTestId('schema-selector').click() + await page.getByRole('option', { name: 'auth' }).click() + + // Wait for the tables list to be visible + await expect( + page.getByTestId('tables-list'), + 'Tables list should be visible in auth schema' + ).toBeVisible() + + // search for users + await page.getByRole('textbox', { name: 'Search tables...' }).fill('users') + + // Try to find the users table directly + const usersTable = page.getByRole('button', { name: 'View users' }) + await expect(usersTable, 'Users table should be visible in auth schema').toBeVisible() + + // go back to public schema + await page.getByTestId('schema-selector').click() + await page.getByRole('option', { name: 'public', exact: true }).click() + + // wait for the tables list to be visible + await expect( + page.getByTestId('tables-list'), + 'Tables list should be visible in public schema' + ).toBeVisible() + }) +}) diff --git a/e2e/studio/package.json b/e2e/studio/package.json new file mode 100644 index 0000000000..41d49dfa76 --- /dev/null +++ b/e2e/studio/package.json @@ -0,0 +1,16 @@ +{ + "name": "e2e-studio", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "e2e": "playwright test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "dotenv": "^16.5.0", + "@playwright/test": "^1.52.0" + } +} diff --git a/e2e/studio/playwright.config.ts b/e2e/studio/playwright.config.ts new file mode 100644 index 0000000000..ce5f2dbee8 --- /dev/null +++ b/e2e/studio/playwright.config.ts @@ -0,0 +1,45 @@ +import { defineConfig } from '@playwright/test' +import { env, STORAGE_STATE_PATH } from './env.config' +import dotenv from 'dotenv' +import path from 'path' + +dotenv.config({ path: path.resolve(__dirname, '.env.local') }) + +const IS_CI = !!process.env.CI + +export default defineConfig({ + timeout: 60 * 1000, + testDir: './features', + testMatch: /.*\.spec\.ts/, + forbidOnly: IS_CI, + retries: 3, + use: { + baseURL: env.STUDIO_URL, + screenshot: 'off', + video: 'retain-on-failure', + headless: IS_CI, + trace: 'retain-on-failure', + }, + projects: [ + { + name: 'setup', + testMatch: /.*\.setup\.ts/, + }, + { + name: 'Features', + testDir: './features', + testMatch: /.*\.spec\.ts/, + dependencies: ['setup'], + use: { + browserName: 'chromium', + screenshot: 'off', + storageState: STORAGE_STATE_PATH, + }, + }, + ], + reporter: [ + ['list'], + ['html', { open: 'never' }], + ['json', { outputFile: 'test-results/test-results.json' }], + ], +}) diff --git a/e2e/studio/tsconfig.json b/e2e/studio/tsconfig.json new file mode 100644 index 0000000000..fa766b8295 --- /dev/null +++ b/e2e/studio/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "jsx": "react", + "skipLibCheck": true, + "esModuleInterop": true + } +} diff --git a/e2e/studio/utils/dismiss-toast.ts b/e2e/studio/utils/dismiss-toast.ts new file mode 100644 index 0000000000..ed61980dc5 --- /dev/null +++ b/e2e/studio/utils/dismiss-toast.ts @@ -0,0 +1,11 @@ +import { Page } from '@playwright/test' + +export const dismissToast = async (page: Page) => { + await page + .locator('li.toast') + .getByRole('button', { name: 'Opt out' }) + .waitFor({ state: 'visible' }) + await page.locator('li.toast').getByRole('button', { name: 'Opt out' }).click() +} + +export const toKebabCase = (str: string) => str.replace(/([A-Z])/g, '-$1').toLowerCase() diff --git a/e2e/studio/utils/test.ts b/e2e/studio/utils/test.ts new file mode 100644 index 0000000000..2d96f75db9 --- /dev/null +++ b/e2e/studio/utils/test.ts @@ -0,0 +1,21 @@ +import { test as base } from '@playwright/test' +import dotenv from 'dotenv' +import path from 'path' +import { env } from '../env.config' + +dotenv.config({ + path: path.resolve(__dirname, '../.env.local'), + override: true, +}) + +export interface TestOptions { + env: string + ref: string + apiUrl: string +} + +export const test = base.extend({ + env: env.STUDIO_URL, + ref: env.PROJECT_REF, + apiUrl: env.API_URL, +}) diff --git a/e2e/studio/utils/to-url.ts b/e2e/studio/utils/to-url.ts new file mode 100644 index 0000000000..af0bb8b988 --- /dev/null +++ b/e2e/studio/utils/to-url.ts @@ -0,0 +1,5 @@ +import { env } from '../env.config' + +export function toUrl(path: `/${string}`) { + return `${env.STUDIO_URL}${path}` +} diff --git a/package.json b/package.json index 3e8afe69ae..69af2e1c8e 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,17 @@ "test:ui-patterns": "turbo run test --filter=ui-patterns", "test:studio": "turbo run test --filter=studio", "test:studio:watch": "turbo run test --filter=studio -- watch", - "test:e2e:studio-local": "pnpm --prefix tests/studio-tests run test:local", - "test:e2e:studio-staging": "pnpm --prefix tests/studio-tests run test:staging", + + "e2e": "pnpm --prefix e2e/studio run e2e", + "e2e:dev-hosted": "pnpm --prefix e2e/studio run e2e:dev-hosted", + "e2e:dev-selfhosted": "pnpm --prefix e2e/studio run e2e:dev-selfhosted", + "e2e:selfhosted": "pnpm --prefix e2e/studio run e2e:selfhosted", + "e2e:staging": "pnpm --prefix e2e/studio run e2e:staging", + "e2e:prod": "pnpm --prefix e2e/studio run e2e:prod", + "e2e:ci": "pnpm --prefix e2e/studio run e2e:ci", + "e2e:supabase:start": "pnpm --prefix e2e/studio run supabase:start", + "e2e:supabase:stop": "pnpm --prefix e2e/studio run supabase:stop", + "perf:kong": "ab -t 5 -c 20 -T application/json http://localhost:8000/", "perf:meta": "ab -t 5 -c 20 -T application/json http://localhost:5555/tables", "generate:types": "supabase gen types typescript --local > ./supabase/functions/common/database-types.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a8b855a62..4097bfed3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,28 +91,28 @@ importers: version: 3.40.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@payloadcms/next': specifier: 3.33.0 - version: 3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + version: 3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) '@payloadcms/payload-cloud': specifier: 3.33.0 version: 3.33.0(encoding@0.1.13)(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3)) '@payloadcms/plugin-form-builder': specifier: 3.33.0 - version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) '@payloadcms/plugin-nested-docs': specifier: 3.33.0 version: 3.33.0(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3)) '@payloadcms/plugin-seo': specifier: 3.33.0 - version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) '@payloadcms/richtext-lexical': specifier: 3.33.0 - version: 3.33.0(@faceless-ui/modal@3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@faceless-ui/scroll-info@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@payloadcms/next@3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3))(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)(yjs@13.6.27) + version: 3.33.0(@faceless-ui/modal@3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@faceless-ui/scroll-info@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@payloadcms/next@3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3))(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)(yjs@13.6.27) '@payloadcms/storage-s3': specifier: 3.33.0 - version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) '@payloadcms/ui': specifier: 3.33.0 - version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + version: 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) common: specifier: workspace:* version: link:../../packages/common @@ -130,7 +130,7 @@ importers: version: 16.10.0 next: specifier: ~15.3.0 - version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) payload: specifier: 3.33.0 version: 3.33.0(graphql@16.10.0)(typescript@5.7.3) @@ -182,10 +182,10 @@ importers: version: 1.2.0 next: specifier: 'catalog:' - version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-contentlayer2: specifier: 0.4.6 - version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) + version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -327,7 +327,7 @@ importers: version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: ^9.15.0 - version: 9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) + version: 9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) '@supabase/supabase-js': specifier: 'catalog:' version: 2.49.3 @@ -435,7 +435,7 @@ importers: version: 1.0.1 next: specifier: 15.3.1 - version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-mdx-remote: specifier: ^4.4.1 version: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) @@ -447,7 +447,7 @@ importers: version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nuqs: specifier: ^1.19.1 - version: 1.19.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)) + version: 1.19.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)) openai: specifier: ^4.20.1 version: 4.71.1(encoding@0.1.13)(zod@3.23.8) @@ -700,7 +700,7 @@ importers: version: 0.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: ^8.52.1 - version: 8.52.1(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) + version: 8.52.1(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) '@std/path': specifier: npm:@jsr/std__path@^1.0.8 version: '@jsr/std__path@1.0.8' @@ -739,7 +739,7 @@ importers: version: 2.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@vercel/flags': specifier: ^2.6.0 - version: 2.6.0(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.6.0(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(supports-color@8.1.1)(vite@6.2.6(@types/node@20.12.11)(jiti@2.4.2)(sass@1.77.4)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5)) @@ -826,13 +826,13 @@ importers: version: 0.52.2 next: specifier: ~15.3.0 - version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nuqs: specifier: ^2.4.1 - version: 2.4.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 2.4.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) openai: specifier: ^4.20.1 version: 4.71.1(encoding@0.1.13)(zod@3.23.8) @@ -1085,7 +1085,7 @@ importers: version: 2.4.11(typescript@5.5.2) next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) + version: 0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) postcss: specifier: ^8.5.3 version: 8.5.3 @@ -1172,10 +1172,10 @@ importers: version: 0.436.0(react@18.3.1) next: specifier: 'catalog:' - version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-contentlayer2: specifier: 0.4.6 - version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) + version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1407,16 +1407,16 @@ importers: version: 1.2.0 next: specifier: 'catalog:' - version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-contentlayer2: specifier: 0.5.3 - version: 0.5.3(contentlayer2@0.5.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) + version: 0.5.3(contentlayer2@0.5.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) next-mdx-remote: specifier: ^4.4.1 version: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) next-seo: specifier: ^6.5.0 - version: 6.5.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.5.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1551,6 +1551,15 @@ importers: specifier: ^3.22.4 version: 3.23.8 + e2e/studio: + dependencies: + '@playwright/test': + specifier: ^1.52.0 + version: 1.52.0 + dotenv: + specifier: ^16.5.0 + version: 16.5.0 + packages/ai-commands: dependencies: '@serafin/schema-builder': @@ -1661,7 +1670,7 @@ importers: version: 4.42.0 '@vercel/flags': specifier: ^2.6.0 - version: 2.6.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.6.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) api-types: specifier: workspace:* version: link:../api-types @@ -1676,7 +1685,7 @@ importers: version: 4.17.21 next: specifier: 'catalog:' - version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1954,7 +1963,7 @@ importers: version: 0.436.0(react@18.3.1) next: specifier: 'catalog:' - version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2123,7 +2132,7 @@ importers: version: 0.52.2 next: specifier: '*' - version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-themes: specifier: '*' version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2232,7 +2241,7 @@ importers: version: link:../api-types next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) + version: 0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) tsx: specifier: ^4.19.3 version: 4.19.3 @@ -2252,18 +2261,6 @@ importers: specifier: ^3.0.5 version: 3.0.9(@types/node@20.12.11)(jiti@2.4.2)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.7.3(@types/node@20.12.11)(typescript@5.5.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5) - tests/studio-tests: - devDependencies: - '@playwright/test': - specifier: ^1.49.0 - version: 1.49.1 - dotenv: - specifier: ^16.4.7 - version: 16.4.7 - lodash: - specifier: ^4.17.21 - version: 4.17.21 - packages: '@aashutoshrathi/word-wrap@1.2.6': @@ -5816,8 +5813,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.49.1': - resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + '@playwright/test@1.52.0': + resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==} engines: {node: '>=18'} hasBin: true @@ -10858,6 +10855,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dotenv@16.5.0: + resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + engines: {node: '>=12'} + dotty@0.1.2: resolution: {integrity: sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ==} @@ -14907,13 +14908,13 @@ packages: pkg-types@2.1.0: resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} - playwright-core@1.49.1: - resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + playwright-core@1.52.0: + resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} engines: {node: '>=18'} hasBin: true - playwright@1.49.1: - resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + playwright@1.52.0: + resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==} engines: {node: '>=18'} hasBin: true @@ -17508,11 +17509,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - use-sync-external-store@1.4.0: - resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - use-sync-external-store@1.5.0: resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} peerDependencies: @@ -20300,8 +20296,8 @@ snapshots: '@fastify/ajv-compiler@3.6.0': dependencies: - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) fast-uri: 2.4.0 '@fastify/busboy@3.1.1': {} @@ -22825,12 +22821,12 @@ snapshots: '@payloadcms/live-preview@3.40.0': {} - '@payloadcms/next@3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': + '@payloadcms/next@3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': dependencies: '@dnd-kit/core': 6.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@payloadcms/graphql': 3.33.0(graphql@16.10.0)(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(typescript@5.7.3) '@payloadcms/translations': 3.33.0 - '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) busboy: 1.6.0 dequal: 2.0.3 file-type: 19.3.0 @@ -22838,7 +22834,7 @@ snapshots: graphql-http: 1.22.4(graphql@16.10.0) graphql-playground-html: 1.6.30 http-status: 2.1.0 - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) path-to-regexp: 6.3.0 payload: 3.33.0(graphql@16.10.0)(typescript@5.7.3) qs-esm: 7.0.2 @@ -22867,9 +22863,9 @@ snapshots: - aws-crt - encoding - '@payloadcms/plugin-cloud-storage@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': + '@payloadcms/plugin-cloud-storage@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': dependencies: - '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) find-node-modules: 2.1.3 payload: 3.33.0(graphql@16.10.0)(typescript@5.7.3) range-parser: 1.2.1 @@ -22882,9 +22878,9 @@ snapshots: - supports-color - typescript - '@payloadcms/plugin-form-builder@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': + '@payloadcms/plugin-form-builder@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': dependencies: - '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) escape-html: 1.0.3 payload: 3.33.0(graphql@16.10.0)(typescript@5.7.3) react: 18.3.1 @@ -22900,10 +22896,10 @@ snapshots: dependencies: payload: 3.33.0(graphql@16.10.0)(typescript@5.7.3) - '@payloadcms/plugin-seo@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': + '@payloadcms/plugin-seo@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': dependencies: '@payloadcms/translations': 3.33.0 - '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) payload: 3.33.0(graphql@16.10.0)(typescript@5.7.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22914,7 +22910,7 @@ snapshots: - supports-color - typescript - '@payloadcms/richtext-lexical@3.33.0(@faceless-ui/modal@3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@faceless-ui/scroll-info@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@payloadcms/next@3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3))(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)(yjs@13.6.27)': + '@payloadcms/richtext-lexical@3.33.0(@faceless-ui/modal@3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@faceless-ui/scroll-info@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@payloadcms/next@3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3))(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)(yjs@13.6.27)': dependencies: '@faceless-ui/modal': 3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@faceless-ui/scroll-info': 2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -22928,9 +22924,9 @@ snapshots: '@lexical/selection': 0.28.0 '@lexical/table': 0.28.0 '@lexical/utils': 0.28.0 - '@payloadcms/next': 3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + '@payloadcms/next': 3.33.0(@types/react@18.3.3)(graphql@16.10.0)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) '@payloadcms/translations': 3.33.0 - '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + '@payloadcms/ui': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) '@types/uuid': 10.0.0 acorn: 8.12.1 bson-objectid: 2.0.4 @@ -22956,12 +22952,12 @@ snapshots: - typescript - yjs - '@payloadcms/storage-s3@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': + '@payloadcms/storage-s3@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': dependencies: '@aws-sdk/client-s3': 3.821.0 '@aws-sdk/lib-storage': 3.821.0(@aws-sdk/client-s3@3.821.0) '@aws-sdk/s3-request-presigner': 3.821.0 - '@payloadcms/plugin-cloud-storage': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) + '@payloadcms/plugin-cloud-storage': 3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3) payload: 3.33.0(graphql@16.10.0)(typescript@5.7.3) transitivePeerDependencies: - '@types/react' @@ -22977,7 +22973,7 @@ snapshots: dependencies: date-fns: 4.1.0 - '@payloadcms/ui@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': + '@payloadcms/ui@3.33.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.33.0(graphql@16.10.0)(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.7.3)': dependencies: '@date-fns/tz': 1.2.0 '@dnd-kit/core': 6.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -22991,7 +22987,7 @@ snapshots: date-fns: 4.1.0 dequal: 2.0.3 md5: 2.3.0 - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) object-to-formdata: 4.5.1 payload: 3.33.0(graphql@16.10.0)(typescript@5.7.3) qs-esm: 7.0.2 @@ -23016,9 +23012,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.49.1': + '@playwright/test@1.52.0': dependencies: - playwright: 1.49.1 + playwright: 1.52.0 '@polka/url@1.0.0-next.25': {} @@ -23727,7 +23723,7 @@ snapshots: '@radix-ui/react-popper@1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/react-dom': 2.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-arrow': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.3)(react@18.3.1) @@ -24561,7 +24557,7 @@ snapshots: chokidar: 3.6.0 colorette: 1.4.0 core-js: 3.35.0 - dotenv: 16.4.7 + dotenv: 16.5.0 form-data: 4.0.2 get-port-please: 3.1.2 glob: 7.2.3 @@ -24943,7 +24939,7 @@ snapshots: '@babel/core': 7.26.10(supports-color@8.1.1) '@sentry/babel-plugin-component-annotate': 2.22.7 '@sentry/cli': 2.39.1(encoding@0.1.13)(supports-color@8.1.1) - dotenv: 16.4.7 + dotenv: 16.5.0 find-up: 5.0.0 glob: 9.3.5 magic-string: 0.30.8 @@ -25050,7 +25046,7 @@ snapshots: '@sentry/core@9.15.0': {} - '@sentry/nextjs@8.52.1(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@8.52.1(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 @@ -25063,7 +25059,7 @@ snapshots: '@sentry/vercel-edge': 8.52.1 '@sentry/webpack-plugin': 2.22.7(encoding@0.1.13)(supports-color@8.1.1)(webpack@5.94.0) chalk: 3.0.0 - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -25076,7 +25072,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.32.0 @@ -25089,7 +25085,7 @@ snapshots: '@sentry/vercel-edge': 9.15.0 '@sentry/webpack-plugin': 3.3.1(encoding@0.1.13)(supports-color@8.1.1)(webpack@5.94.0) chalk: 3.0.0 - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) resolve: 1.22.8 rollup: 4.35.0 stacktrace-parser: 0.1.10 @@ -26425,7 +26421,7 @@ snapshots: '@tanstack/store': 0.7.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.4.0(react@18.3.1) + use-sync-external-store: 1.5.0(react@18.3.1) '@tanstack/react-virtual@3.13.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -27298,19 +27294,19 @@ snapshots: lz-string: 1.5.0 uuid: 9.0.1 - '@vercel/flags@2.6.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@vercel/flags@2.6.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: jose: 5.2.1 optionalDependencies: - next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@vercel/flags@2.6.0(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@vercel/flags@2.6.0(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: jose: 5.2.1 optionalDependencies: - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -27785,9 +27781,9 @@ snapshots: optionalDependencies: ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.17.1): + ajv-formats@3.0.1(ajv@8.12.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.12.0 ajv-keywords@3.5.2(ajv@6.12.6): dependencies: @@ -27828,7 +27824,7 @@ snapshots: js-cookie: 2.2.1 transitivePeerDependencies: - encoding - + animejs@4.0.2: {} anser@2.3.2: {} @@ -28254,7 +28250,7 @@ snapshots: chokidar: 4.0.3 confbox: 0.1.8 defu: 6.1.4 - dotenv: 16.4.7 + dotenv: 16.5.0 exsolve: 1.0.4 giget: 2.0.0 jiti: 2.4.2 @@ -29290,6 +29286,8 @@ snapshots: dotenv@16.4.7: {} + dotenv@16.5.0: {} + dotty@0.1.2: {} drange@1.1.1: {} @@ -30147,8 +30145,8 @@ snapshots: fast-json-stringify@5.16.1: dependencies: '@fastify/merge-json-schemas': 0.1.1 - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.12.0 + ajv-formats: 3.0.1(ajv@8.12.0) fast-deep-equal: 3.1.3 fast-uri: 2.4.0 json-schema-ref-resolver: 1.0.1 @@ -33645,12 +33643,12 @@ snapshots: neo-async@2.6.2: {} - next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): + next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.4.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.4.3 contentlayer2: 0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -33659,12 +33657,12 @@ snapshots: - markdown-wasm - supports-color - next-contentlayer2@0.5.3(contentlayer2@0.5.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): + next-contentlayer2@0.5.3(contentlayer2@0.5.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.5.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.5.3 contentlayer2: 0.5.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -33688,14 +33686,14 @@ snapshots: dependencies: js-yaml-loader: 1.2.2 - next-router-mock@0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): + next-router-mock@0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): dependencies: - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 - next-seo@6.5.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-seo@6.5.0(next@14.2.26(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -33706,7 +33704,7 @@ snapshots: next-tick@1.1.0: {} - next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): + next@14.2.26(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): dependencies: '@next/env': 14.2.26 '@swc/helpers': 0.5.5 @@ -33728,13 +33726,13 @@ snapshots: '@next/swc-win32-ia32-msvc': 14.2.26 '@next/swc-win32-x64-msvc': 14.2.26 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.49.1 + '@playwright/test': 1.52.0 sass: 1.77.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): + next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): dependencies: '@next/env': 15.3.1 '@swc/counter': 0.1.3 @@ -33755,7 +33753,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 15.3.1 '@next/swc-win32-x64-msvc': 15.3.1 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.49.1 + '@playwright/test': 1.52.0 sass: 1.77.4 sharp: 0.34.1 transitivePeerDependencies: @@ -34053,17 +34051,17 @@ snapshots: number-flow@0.3.7: {} - nuqs@1.19.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)): + nuqs@1.19.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)): dependencies: mitt: 3.0.1 - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) - nuqs@2.4.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + nuqs@2.4.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: mitt: 3.0.1 react: 18.3.1 optionalDependencies: - next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react-router: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nwsapi@2.2.20: @@ -34514,7 +34512,7 @@ snapshots: periscopic@3.1.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.7 estree-walker: 3.0.3 is-reference: 3.0.3 @@ -34712,11 +34710,11 @@ snapshots: exsolve: 1.0.4 pathe: 2.0.3 - playwright-core@1.49.1: {} + playwright-core@1.52.0: {} - playwright@1.49.1: + playwright@1.52.0: dependencies: - playwright-core: 1.49.1 + playwright-core: 1.52.0 optionalDependencies: fsevents: 2.3.2 @@ -36628,7 +36626,7 @@ snapshots: static-browser-server@1.0.3: dependencies: '@open-draft/deferred-promise': 2.2.0 - dotenv: 16.4.7 + dotenv: 16.5.0 mime-db: 1.53.0 outvariant: 1.4.3 @@ -36935,7 +36933,7 @@ snapshots: dependencies: client-only: 0.0.1 react: 18.3.1 - use-sync-external-store: 1.4.0(react@18.3.1) + use-sync-external-store: 1.5.0(react@18.3.1) swrev@4.0.0: {} @@ -37874,10 +37872,6 @@ snapshots: dependencies: react: 18.3.1 - use-sync-external-store@1.4.0(react@18.3.1): - dependencies: - react: 18.3.1 - use-sync-external-store@1.5.0(react@18.3.1): dependencies: react: 18.3.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 137fe7e0cf..f2ed1040ed 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,7 @@ packages: - 'apps/*' - 'packages/*' - - 'tests/*' + - 'e2e/*' catalog: '@types/node': ^20.0.0 diff --git a/tests/studio-tests/.env.staging.example b/tests/studio-tests/.env.staging.example deleted file mode 100644 index 9595e3fa1f..0000000000 --- a/tests/studio-tests/.env.staging.example +++ /dev/null @@ -1,3 +0,0 @@ -EMAIL= -PASSWORD= -PROJECT_REF= \ No newline at end of file diff --git a/tests/studio-tests/README.md b/tests/studio-tests/README.md deleted file mode 100644 index e877f2fc67..0000000000 --- a/tests/studio-tests/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Studio E2E Tests - -In an effort to make the local + hosted studio more stable, we've added tests which test features which are commonly used in local -development. Built with [Playwright](https://playwright.dev/docs/intro). - -## How to run tests - -Before running the tests, make sure you've done the following: - -- Run `npm install` in the root folder of this repo -- `docker` or `orbstack` is currently running -- `supabase` CLI is already [installed](https://github.com/supabase/cli?tab=readme-ov-file#install-the-cli) -- no other supabase local environment is running (infrastructure environment is ok) - -You can run the tests by running `npm run test` in this folder. - -When you run the command, it includes: - -1. Setting up the local environment using the `supabase` CLI -2. Extracting the environment variables and saving them into a `.env.test` file in the studio app -3. Running the `studio` app in dev mode (`npm run dev`) -4. Running the tests -5. Stopping the `studio` app and the local environment - -If the environment does't stop for some reason, you'll see `supabase start is already running` on the next run. In this -case, just run `supabase stop` between test runs. - -## How to write tests - -Playwright has a nice [Codegen tool](https://playwright.dev/docs/codegen-intro#running-codegen) which you can use to -record your actions: - -```bash -pnpm codegen:setup -# in a separate terminal -pnpm codegen -``` - -## How to debug/fix tests - -If you've run the tests locally and you want to see the results, Playwright has a [UI mode](https://playwright.dev/docs/test-ui-mode) -which you can use to run and replay specific tests: - -```bash -pnpm test -- --ui -``` - -It will also record any failing tests when running `npm run test` in this folder. diff --git a/tests/studio-tests/base.ts b/tests/studio-tests/base.ts deleted file mode 100644 index 5484171153..0000000000 --- a/tests/studio-tests/base.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { test as base } from '@playwright/test' -import dotenv from 'dotenv' -import path from 'path' - -dotenv.config({ - path: path.resolve(__dirname, process.env.ENV === 'staging' ? '../.env.staging' : ''), - override: true, -}) - -export interface TestOptions { - env: string - ref: string - apiUrl: string -} - -export const test = base.extend({ - env: process.env.ENV, - ref: process.env.PROJECT_REF, - apiUrl: - process.env.ENV === 'local' - ? 'http://localhost:8082/api' - : process.env.ENV === 'staging' - ? 'https://api.supabase.green' - : '', -}) diff --git a/tests/studio-tests/generate-local-env.js b/tests/studio-tests/generate-local-env.js deleted file mode 100644 index d51ffb3775..0000000000 --- a/tests/studio-tests/generate-local-env.js +++ /dev/null @@ -1,37 +0,0 @@ -const fs = require('fs') -const generatedEnv = require('./keys.json') - -/** - * This script takes the API keys from the local environment, merges them with some predefined variables and saves them - * to a env.test file in the studio app. This is needed to prepare the studio so that it can be run with the local - * environment as the backend. - */ - -const defaultEnv = { - // POSTGRES_PASSWORD: 'postgres', - // NEXT_ANALYTICS_BACKEND_PROVIDER: 'postgres', - // SUPABASE_REST_URL: 'http://127.0.0.1:54321/rest/v1/', - // NEXT_PUBLIC_ENABLE_LOGS: 'false', - // NEXT_PUBLIC_IS_PLATFORM: 'false', - SUPABASE_ANON_KEY: '$ANON_KEY', - SUPABASE_SERVICE_KEY: '$SERVICE_ROLE_KEY', - SUPABASE_URL: '$API_URL', - STUDIO_PG_META_URL: '$API_URL/pg', - SUPABASE_PUBLIC_URL: '$API_URL', - SENTRY_IGNORE_API_RESOLUTION_ERROR: '1', - LOGFLARE_URL: 'http://localhost:54329', - LOGFLARE_API_KEY: 'api-key', - NEXT_PUBLIC_SITE_URL: 'http://localhost:8082', - NEXT_PUBLIC_GOTRUE_URL: '$SUPABASE_PUBLIC_URL/auth/v1', - NEXT_PUBLIC_HCAPTCHA_SITE_KEY: '10000000-ffff-ffff-ffff-000000000001', - NEXT_PUBLIC_NODE_ENV: 'test', -} - -const environment = { ...generatedEnv, ...defaultEnv } - -fs.writeFileSync( - '../../apps/studio/.env.test', - Object.keys(environment) - .map((key) => `${key}=${environment[key]}`) - .join('\n') -) diff --git a/tests/studio-tests/package.json b/tests/studio-tests/package.json deleted file mode 100644 index b96c92fd6a..0000000000 --- a/tests/studio-tests/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "studio-tests", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "preinstall": "npx only-allow pnpm", - "env:setup": "supabase start -x studio && supabase status --output json > keys.json && node generate-local-env.js", - "pretest:local": "pnpm env:setup", - "test:local": "pnpm env:setup && export ENV=local && export PROJECT_REF=default && playwright test", - "posttest:local": "supabase stop --no-backup", - "test:staging": "export ENV=staging && playwright test", - "codegen:setup": "pnpm env:setup && NODE_ENV=test pnpm --prefix ../../apps/studio dev", - "codegen": "playwright codegen http://localhost:8082/project/default", - "clean": "rimraf node_modules" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@playwright/test": "^1.49.0", - "lodash": "^4.17.21", - "dotenv": "^16.4.7" - } -} diff --git a/tests/studio-tests/playwright.config.ts b/tests/studio-tests/playwright.config.ts deleted file mode 100644 index 187369266f..0000000000 --- a/tests/studio-tests/playwright.config.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' - -// See https://playwright.dev/docs/test-configuration. - -export default defineConfig({ - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - timeout: 20000, - reporter: 'html', - use: { - baseURL: - process.env.ENV === 'local' - ? 'http://localhost:8082' - : process.env.ENV === 'staging' - ? 'https://supabase.green/dashboard/' - : '', - trace: 'on-first-retry', - video: 'retain-on-failure', - launchOptions: { - env: { - NODE_ENV: - process.env.ENV === 'local' ? 'test' : process.env.ENV === 'staging' ? 'staging' : '', - }, - }, - }, - projects: [ - ...(process.env.ENV !== 'local' - ? [ - { - name: 'Authentication setup', - testDir: './tests', - testMatch: /.*\.setup\.ts/, - }, - ] - : []), - { - name: 'Common Functionality', - use: { - ...devices['Desktop Chrome'], - ...(process.env.ENV !== 'local' ? { storageState: 'playwright/.auth/user.json' } : {}), - viewport: { width: 1366, height: 768 }, - }, - testDir: './tests/common-functionality', - dependencies: process.env.ENV !== 'local' ? ['Authentication setup'] : undefined, - }, - ...(process.env.ENV !== 'local' - ? [ - { - name: 'Production Functionality', - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/user.json', - viewport: { width: 1366, height: 768 }, - }, - testDir: './tests/production-functionality', - dependencies: ['Authentication setup'], - }, - ] - : []), - ], - /* Run your local dev server before starting the tests */ - webServer: - process.env.ENV === 'local' - ? { - // using npm run dev instead of turbo because turbo doesn't stop the server after a test (doesn't handle SIGTERM). - command: 'pnpm dev', - cwd: '../../apps/studio', - url: 'http://localhost:8082', - reuseExistingServer: !process.env.CI, - env: { NODE_ENV: 'test' }, - } - : undefined, -}) diff --git a/tests/studio-tests/supabase/.gitignore b/tests/studio-tests/supabase/.gitignore deleted file mode 100644 index a3ad88055b..0000000000 --- a/tests/studio-tests/supabase/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Supabase -.branches -.temp -.env diff --git a/tests/studio-tests/supabase/config.toml b/tests/studio-tests/supabase/config.toml deleted file mode 100644 index 4a8de47428..0000000000 --- a/tests/studio-tests/supabase/config.toml +++ /dev/null @@ -1,159 +0,0 @@ -# A string used to distinguish different Supabase projects on the same host. Defaults to the -# working directory name when running `supabase init`. -project_id = "local-studio-tests" - -[api] -enabled = true -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. public and storage are always included. -schemas = ["public", "storage", "graphql_public"] -# Extra schemas to add to the search_path of every request. public is always included. -extra_search_path = ["public", "extensions"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[db] -# Port to use for the local database URL. -port = 54322 -# Port used by db diff command to initialize the shadow database. -shadow_port = 54320 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 15 - -[db.pooler] -enabled = false -# Port to use for the local connection pooler. -port = 54329 -# Specifies when a server connection can be reused by other clients. -# Configure one of the supported pooler modes: `transaction`, `session`. -pool_mode = "transaction" -# How many server connections to allow per user/database pair. -default_pool_size = 20 -# Maximum number of client connections allowed. -max_client_conn = 100 - -[realtime] -enabled = true -# Bind realtime via either IPv4 or IPv6. (default: IPv6) -# ip_version = "IPv6" -# The maximum length in bytes of HTTP request headers. (default: 4096) -# max_header_length = 4096 - -[studio] -enabled = true -# Port to use for Supabase Studio. -port = 54323 -# External URL of the API server that frontend connects to. -api_url = "http://127.0.0.1" - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -enabled = true -# Port to use for the email testing server web interface. -port = 54324 -# Uncomment to expose additional ports for testing user applications that send emails. -# smtp_port = 54325 -# pop3_port = 54326 - -[storage] -enabled = true -# The maximum file size allowed (e.g. "5MB", "500KB"). -file_size_limit = "50MiB" - -[auth] -enabled = true -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://127.0.0.1:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://127.0.0.1:3000"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). -jwt_expiry = 3600 -# If disabled, the refresh token will never expire. -enable_refresh_token_rotation = true -# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. -# Requires enable_refresh_token_rotation = true. -refresh_token_reuse_interval = 10 -# Allow/disallow new user signups to your project. -enable_signup = true -# Allow/disallow testing manual linking of accounts -# enable_manual_linking = false - -[auth.email] -# Allow/disallow new user signups via email to your project. -enable_signup = true -# If enabled, a user will be required to confirm any email change on both the old, and new email -# addresses. If disabled, only the new email is required to confirm. -double_confirm_changes = true -# If enabled, users need to confirm their email address before signing in. -enable_confirmations = false - -# Uncomment to customize email template -# [auth.email.template.invite] -# subject = "You have been invited" -# content_path = "./supabase/templates/invite.html" - -[auth.sms] -# Allow/disallow new user signups via SMS to your project. -enable_signup = true -# If enabled, users need to confirm their phone number before signing in. -enable_confirmations = false -# Template for sending OTP to users -# template = "Your code is {{ .Code }} ." - -# Use pre-defined map of phone number to OTP for testing. -[auth.sms.test_otp] -# 4152127777 = "123456" - -# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. -# [auth.hook.custom_access_token] -# enabled = true -# uri = "pg-functions:////" - - -# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. -[auth.sms.twilio] -enabled = false -account_sid = "" -message_service_sid = "" -# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: -auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" - -# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, -# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, -# `twitter`, `slack`, `spotify`, `workos`, `zoom`. -[auth.external.apple] -enabled = false -client_id = "" -# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: -secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" -# Overrides the default auth redirectUrl. -redirect_uri = "" -# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, -# or any other third-party OIDC providers. -url = "" - -[analytics] -enabled = true -port = 54329 -vector_port = 54328 -# Configure one of the supported backends: `postgres`, `bigquery`. -backend = "postgres" - -# Experimental features may be deprecated any time -# [experimental] -# Configures Postgres storage engine to use OrioleDB (S3) -# orioledb_version = "" -# Configures S3 bucket URL, eg. .s3-.amazonaws.com -# s3_host = "env(S3_HOST)" -# Configures S3 bucket region, eg. us-east-1 -# s3_region = "env(S3_REGION)" -# Configures AWS_ACCESS_KEY_ID for S3 bucket -# s3_access_key = "env(S3_ACCESS_KEY)" -# Configures AWS_SECRET_ACCESS_KEY for S3 bucket -# s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/tests/studio-tests/supabase/seed.sql b/tests/studio-tests/supabase/seed.sql deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/studio-tests/tests/auth.setup.ts b/tests/studio-tests/tests/auth.setup.ts deleted file mode 100644 index 4bcd271525..0000000000 --- a/tests/studio-tests/tests/auth.setup.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { test as setup } from '@playwright/test' -import dotenv from 'dotenv' -import path from 'path' - -dotenv.config({ - path: path.resolve(__dirname, process.env.ENV === 'staging' ? '../.env.staging' : ''), - override: true, -}) -const authFile = path.join(__dirname, '../playwright/.auth/user.json') - -setup('Authenticate', async ({ page }) => { - await page.goto('./sign-in') - await page.getByLabel('Email').fill(process.env.EMAIL ?? '') - await page.getByLabel('Password').fill(process.env.PASSWORD ?? '') - await page.getByRole('button', { name: 'Sign In' }).click() - - await page.pause() - - await page.waitForURL('./projects') - await page.context().storageState({ path: authFile }) -}) diff --git a/tests/studio-tests/tests/common-functionality/home.spec.ts b/tests/studio-tests/tests/common-functionality/home.spec.ts deleted file mode 100644 index 9a590ce7a4..0000000000 --- a/tests/studio-tests/tests/common-functionality/home.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { expect } from '@playwright/test' -import { test } from '../../base' - -test.describe('Project', async () => { - test('Can navigate to project home page', async ({ page, env, ref }) => { - await page.goto(`./project/${ref}`) - await expect( - page.getByRole('heading', { - name: env === 'local' ? 'Welcome to your project' : 'Playwright Test', - }) - ).toBeVisible({ timeout: 10000 }) - }) -}) diff --git a/tests/studio-tests/tests/common-functionality/logs.spec.ts b/tests/studio-tests/tests/common-functionality/logs.spec.ts deleted file mode 100644 index d272a9d37e..0000000000 --- a/tests/studio-tests/tests/common-functionality/logs.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { expect } from '@playwright/test' -import { test } from '../../base' - -const LOGS_PAGES = [ - { label: 'API Gateway', route: 'edge-logs' }, - { label: 'Postgres', route: 'postgres-logs' }, - { label: 'PostgREST', route: 'postgrest-logs' }, -] - -test.describe('Logs', async () => { - test.describe.configure({ retries: 5 }) - for (const logPage of LOGS_PAGES) { - test.describe(`${logPage.label} logs page`, () => { - test('can navigate to logs page', async ({ page, ref }) => { - await page.goto(`./project/${ref}`) - await page.locator('a', { hasText: 'Logs' }).click({ timeout: 10000 }) - await expect(page.getByRole('heading', { name: 'Logs & Analytics' })).toBeVisible() - - // Click anywhere on the screen to close the sidebar - await page.click('body') - - await page - .getByRole('link', { name: logPage.label, exact: true }) - .click() - .catch((e) => { - console.log('๐Ÿ”ด Error clicking', logPage, e) - throw e - }) - - // Wait for and verify the logs table is present - const logsTable = page.getByRole('table') - await expect(logsTable).toBeVisible({ - timeout: 20000, - }) - }) - - test('shows logs data without errors', async ({ page, ref, apiUrl }) => { - // Navigate to page first - await page.goto(`./project/${ref}/logs/${logPage.route}`) - - await page.waitForResponse((response) => - response - .url() - .includes( - `${apiUrl}/platform/projects/${ref}/analytics/endpoints/logs.all?project=${ref}` - ) - ) - - // Wait a bit and check for errors with a longer timeout - const error = page.getByText('Error fetching logs') - await expect(error).not.toBeVisible({ timeout: 10000 }) - - const emptyState = page.getByText('No results found') - if (await emptyState.isVisible()) { - // Empty state, no need to check further - } else { - // Check if the logs table has any rows - const gridcells = page.getByRole('gridcell') - await expect(gridcells.first()).toBeVisible({ timeout: 20000 }) - } - }) - - test('can select and view log details', async ({ page, ref, apiUrl }) => { - // Navigate to page first - await page.goto(`./project/${ref}/logs/${logPage.route}`) - - await page.waitForResponse((response) => - response - .url() - .includes( - `${apiUrl}/platform/projects/${ref}/analytics/endpoints/logs.all?project=${ref}` - ) - ) - - const emptyState = page.getByText('No results found') - if (await emptyState.isVisible()) { - // Empty state, no need to check further - } else { - // Check if the logs table has any rows - const gridcells = page.getByRole('gridcell') - - // Click first row and verify details - await gridcells.first().click() - const tabPanel = page.getByTestId('log-selection') - await page.waitForResponse((response) => - response - .url() - .includes( - `${apiUrl}/platform/projects/${ref}/analytics/endpoints/logs.all?project=${ref}` - ) - ) - await expect(tabPanel).toBeVisible() - - const selectionPanelTimestamp = tabPanel.getByTestId('log-selection-timestamp') - await expect(selectionPanelTimestamp).toBeVisible() - - const rawTimestamp = await selectionPanelTimestamp.textContent() - const timestamp = rawTimestamp?.replace('timestamp', '') - const rowText = await gridcells.first().textContent() - expect(rowText).toContain(timestamp) - - // Click second row and verify different content - await gridcells.nth(1).click() - const tabPanelText2 = await tabPanel.textContent() - expect(tabPanelText2).not.toBe(rowText) - } - }) - }) - } -}) diff --git a/tests/studio-tests/tests/common-functionality/table-editor.spec.ts b/tests/studio-tests/tests/common-functionality/table-editor.spec.ts deleted file mode 100644 index d05fe4462f..0000000000 --- a/tests/studio-tests/tests/common-functionality/table-editor.spec.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Page, expect } from '@playwright/test' -import { kebabCase } from 'lodash' -import { test } from '../../base' - -const dismissToast = async (page: Page) => { - await page - .locator('li.toast') - .getByRole('button', { name: 'Opt out' }) - .waitFor({ state: 'visible' }) - await page.locator('li.toast').getByRole('button', { name: 'Opt out' }).click() -} - -test.describe('Table Editor', () => { - test.beforeEach(async ({ page, env, ref, apiUrl }) => { - const tableResponsePromise = page.waitForResponse( - `${apiUrl}/platform/pg-meta/${ref}/query?key=entity-types-public-0`, - { timeout: 0 } - ) - await page.goto(`./project/${ref}/editor`) - if (env !== 'local') await dismissToast(page) - await tableResponsePromise - }) - - test('should create a new table, view its definition, add new rows, sort and filter', async ({ - page, - ref, - apiUrl, - }, testInfo) => { - const tableName = `${kebabCase(testInfo.title).slice(0, 24)}-${testInfo.retry}-${Math.floor(Math.random() * 10000)}` - - // The page has been loaded with the table data, we can now interact with the page - await page.getByRole('button', { name: 'New table', exact: true }).click() - - await page.getByTestId('table-name-input').waitFor({ state: 'visible' }) - await page.getByTestId('table-name-input').click() - await page.getByTestId('table-name-input').fill(tableName) - - // make the built-in created_at column nullable - await page.getByTestId('created_at-extra-options').click() - await page.getByText('Is Nullable').click() - // the force option is needed because the button is obscured by the popover but we just want to close the popover. - await page.getByTestId('created_at-extra-options').click({ force: true }) - - // add a new column and add default value - await page.getByRole('button', { name: 'Add column' }).click() - await page.getByRole('textbox', { name: 'column_name' }).click() - await page.getByRole('textbox', { name: 'column_name' }).fill('defaultValueColumn') - await page.locator('button').filter({ hasText: 'Choose a column type...' }).click() - await page.getByText('Signed two-byte integer').click() - await page.getByTestId('defaultValueColumn-default-value').click() - await page.getByTestId('defaultValueColumn-default-value').fill('2') - - await page.getByRole('button', { name: 'Save' }).waitFor({ state: 'visible' }) - await page.getByRole('button', { name: 'Save' }).click() - - // hide React Query DevTools if present - await page.evaluate(() => { - const devtools = document.querySelector('.ReactQueryDevtools') - if (devtools) { - devtools.remove() - } - }) - - // view its definition - await page.getByText('definition').click() - await expect(page.locator('div.view-lines')).toContainText( - `CREATE TABLE public.${tableName} ( id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, created_at timestamp with time zone NULL DEFAULT now(), \"defaultValueColumn\" smallint NULL DEFAULT '2'::smallint, CONSTRAINT ${tableName}_pkey PRIMARY KEY (id)) TABLESPACE pg_default;` - ) - - // add a new row - await page.getByRole('button', { name: tableName }).click() - await page.getByTestId('table-editor-insert-new-row').click() - await page.getByText('Insert a new row into').click() - await page.getByTestId('defaultValueColumn-input').click() - await page.getByTestId('defaultValueColumn-input').fill('100') - await page.getByTestId('action-bar-save-row').click() - - // add a second row - await page.getByRole('button', { name: tableName }).click() - await page.getByTestId('table-editor-insert-new-row').click() - await page.getByText('Insert a new row into').click() - // the default value should be '100' for defaultValueColumn - await page.getByTestId('action-bar-save-row').click() - - // Wait for both rows to be visible in the grid - await page.waitForResponse((response) => - response.url().includes(`${apiUrl}/platform/pg-meta/${ref}/query`) - ) - await expect(page.getByRole('grid')).toContainText('2') - await expect(page.getByRole('grid')).toContainText('100') - - // Make sure we can see both rows in the grid before sorting - const rows = page.getByRole('row') - await expect(rows).toHaveCount(3) // header row + 2 data rows - - // sort by the a column - await page.getByRole('button', { name: 'Sort' }).click() - await page.getByTestId('table-editor-pick-column-to-sort-button').click() - await page.getByLabel('Pick a column to sort by').getByText('defaultValueColumn').click() - await page.getByRole('button', { name: 'Apply sorting' }).click() - - // Close the sorting dialog - await page.keyboard.down('Escape') - - // expect the row to be sorted by defaultValueColumn. They're inserted in the order 100, 2 - await expect(rows.nth(1)).toContainText('2') - await expect(rows.nth(2)).toContainText('100') - // remove the sorting - await page.getByRole('button', { name: 'Sorted by 1 rule' }).click() - await page.getByRole('dialog').getByRole('button').nth(1).click() - - // filter by a column - await page.getByRole('button', { name: 'Filter' }).click() - await page.getByRole('button', { name: 'Add filter' }).click() - await page.getByRole('button', { name: 'id' }).click() - await page.getByLabel('id').getByText('defaultValueColumn').click() - await page.getByPlaceholder('Enter a value').click() - await page.getByPlaceholder('Enter a value').fill('2') - await page.getByRole('button', { name: 'Apply filter' }).click() - - // Close the filter dialog - await page.keyboard.down('Escape') - - await expect(page.getByRole('grid')).toContainText('2') - await expect(page.getByRole('grid')).not.toContainText('100') - - // Delete the table as clean up - await page.getByLabel(`View ${tableName}`).click() - await page.getByLabel(`View ${tableName}`).getByRole('button').nth(1).click() - await page.getByText('Delete table').click() - await page.getByRole('button', { name: 'Delete' }).click() - await page.waitForResponse((response) => - response.url().includes(`${apiUrl}/platform/pg-meta/${ref}/tables`) - ) - }) - - test('should check the auth schema', async ({ page }) => { - await page.getByTestId('schema-selector').click() - await page.getByRole('option', { name: 'auth' }).click() - - // extract the tables names from the sidebar - const tables = await page - .getByTestId('tables-list') - .innerText() - .then((text) => text.split('\n')) - - // expect the tables list to contain the following tables (additional tables may be present) - expect(tables).toEqual( - expect.arrayContaining([ - 'audit_log_entries', - 'flow_state', - 'identities', - 'instances', - 'mfa_amr_claims', - 'mfa_challenges', - 'mfa_factors', - 'refresh_tokens', - 'saml_providers', - 'saml_relay_states', - 'schema_migrations', - 'sessions', - 'sso_domains', - 'sso_providers', - 'users', - ]) - ) - }) -}) diff --git a/tests/studio-tests/tests/production-functionality/example.spec.ts b/tests/studio-tests/tests/production-functionality/example.spec.ts deleted file mode 100644 index a550cd4481..0000000000 --- a/tests/studio-tests/tests/production-functionality/example.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { expect } from '@playwright/test' -import { test } from '../../base' - -// [Joshen] This file is redundant, so once we start putting in production specific spec tests, can remove this - -test.describe('Example', async () => { - test('Can navigate to project home page', async ({ page, ref }) => { - await page.goto(`./project/${ref}`) - await expect(page.getByRole('heading', { name: 'Playwright Test' })).toBeVisible() - }) -})