E2E Testing Setup Consolidation (#35080)

* add new e2e folder

* add local supabase and confitional storage

* fix e2e selfhosted

* update actions

* add correct e2e folder

* fix e2e actions

* fix action project ids

* fix permissions

* fix script

* fix playwright install

* playwright root

* pnpm i

* fix api rul

* add env docs

* update run script

* only install deps for e2e

* use same dep

* only install deps for tests

* upd lockfile

* use official vercel integration

* use vercel cli

* remove old folder

* fix script

* rm filter

* rename e2e studio package

* fix install browsers

* add polling for vercel build

* use vercel-preview-url package

* undo actions

* rename ci env to ci

* chore:add rls check and make playwright test less flakey (#35348)

* update ci action

* fix paths

* fix browser install

* run ci against staging

* try caching builds

* fix envs

* fix env check

* fix sign in

* fix sign in url

* fix envs and url

* fix caching

* fix race condition in sign in page

* fix race condition in sign in page

* add check to see if being redirected

* fix caching, check IS_PLATFORM var

* log is_platform

* try vercel build

* fix vercel project id

* fix path

* add temp vercel.json file

* fix paths

* undo project id stuff

* rm cwd

* fix path

* fix paths again

* fix path

* fix base url

* try different fix

* fix config base url

* fix base studio url issues

* retain video on fails

* Update e2e/studio/README.md

Co-authored-by: Copple <10214025+kiwicopple@users.noreply.github.com>

* Update e2e/studio/README.md

Co-authored-by: Copple <10214025+kiwicopple@users.noreply.github.com>

* fix env file naming

* undo caching

* rm old tests folder

* fix readme scripts

* rm vercel deploy for now, just run build locally

* fix url

* fix build script

* fix is_platform

* fix stuck studio start

* fix env vars

* retain network and logs on fail for better debugging

* add apiurl env

* back to vercel

* disable catpcha

* fix test

* update environment configuration to remove default URLs for CI and streamline API base URL handling

* fix typeerr

* fix urls in home.spec

* fix urls in logs.spec

* fix urls in sqleditor spec

* fix table editor spec

* add tourl util

* use staging api in ci

* re add base url env var

* fix url in projects page

* fix url in sql editor spec

* fix sign in not waiting for cookies omfg

* fix env var name

* fix sql-editor test

* simplify table removal

* add opt out telemetry step

* fix logs tests

* fix table editor spec

* remove flaky steps from table editor tests

* use vercel deployment events instead of build

* add studio check

* fix condition

* debug event

* rm if

* trigger deploy

* undo ac

* make opt out button step optional, some envs dont hav eit

* use testid for sql run button

* use id instaed of timestamp in logs tests

* empty

* rm retries

* up glbal timeout

* chore: fix failing sql-editor playwright test (#35767)

* chore: fix failing sql-editor playwright test

* chore: minor fixes

* Chore/update playwright config (#35826)

chore: update playwright config

* rm supabase project from e2e tests

* refactor and simplify environments

* fix sql editor test

* fix ci env vars

* fix

* fix on windows

* update readme

* add playwright install script to readme

* rm turbopack trace flag

* npm to pnpm for scripts

* delete ivan lines

---------

Co-authored-by: Michael Ong <minghao_3728@hotmail.com>
Co-authored-by: Copple <10214025+kiwicopple@users.noreply.github.com>
This commit is contained in:
Jordi Enric
2025-06-03 17:37:39 +02:00
committed by GitHub
parent 6e444c5ac6
commit 55c3fdb49a
38 changed files with 1027 additions and 835 deletions

View File

@@ -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

View File

@@ -29,6 +29,7 @@ export const SqlRunButton = ({
disabled={isDisabled}
type="primary"
size="tiny"
data-testid="sql-run-button"
iconRight={
isExecuting ? (
<Loader2 className="animate-spin" size={10} strokeWidth={1.5} />

View File

@@ -184,12 +184,7 @@ const DefaultPreviewSelectionRenderer = ({ log }: { log: PreviewLogData }) => {
)}
{log?.status && <PropertyRow key={'status'} keyName={'status'} value={log.status} />}
{log?.timestamp && (
<PropertyRow
dataTestId="log-selection-timestamp"
key={'timestamp'}
keyName={'timestamp'}
value={log.timestamp}
/>
<PropertyRow key={'timestamp'} keyName={'timestamp'} value={log.timestamp} />
)}
{Object.entries(rest).map(([key, value]) => {
return <PropertyRow key={key} keyName={key} value={value} />

View File

@@ -23,13 +23,15 @@ const LogsLayout = ({ title, children }: PropsWithChildren<LogsLayoutProps>) =>
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])

View File

@@ -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(<MyComponent />, {
nuqs: {
searchParams: {

View File

@@ -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=

143
e2e/studio/README.md Normal file
View File

@@ -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) })
})
```

12
e2e/studio/env.config.ts Normal file
View File

@@ -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')

View File

@@ -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
})

View File

@@ -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 })
})

View File

@@ -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()
})
})

View File

@@ -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')
})
}
})

View File

@@ -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()
})
})

View File

@@ -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()
})
})

16
e2e/studio/package.json Normal file
View File

@@ -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"
}
}

View File

@@ -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' }],
],
})

7
e2e/studio/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"jsx": "react",
"skipLibCheck": true,
"esModuleInterop": true
}
}

View File

@@ -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()

21
e2e/studio/utils/test.ts Normal file
View File

@@ -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<TestOptions>({
env: env.STUDIO_URL,
ref: env.PROJECT_REF,
apiUrl: env.API_URL,
})

View File

@@ -0,0 +1,5 @@
import { env } from '../env.config'
export function toUrl(path: `/${string}`) {
return `${env.STUDIO_URL}${path}`
}

View File

@@ -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",

222
pnpm-lock.yaml generated
View File

@@ -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

View File

@@ -1,7 +1,7 @@
packages:
- 'apps/*'
- 'packages/*'
- 'tests/*'
- 'e2e/*'
catalog:
'@types/node': ^20.0.0

View File

@@ -1,3 +0,0 @@
EMAIL=
PASSWORD=
PROJECT_REF=

View File

@@ -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.

View File

@@ -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<TestOptions>({
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'
: '',
})

View File

@@ -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')
)

View File

@@ -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"
}
}

View File

@@ -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,
})

View File

@@ -1,4 +0,0 @@
# Supabase
.branches
.temp
.env

View File

@@ -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://<database>/<schema>/<hook_name>"
# 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. <bucket_name>.s3-<region>.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)"

View File

@@ -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 })
})

View File

@@ -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 })
})
})

View File

@@ -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)
}
})
})
}
})

View File

@@ -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',
])
)
})
})

View File

@@ -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()
})
})