mirror of
https://github.com/supabase/supabase.git
synced 2026-06-19 19:47:55 +08:00
Pulls the framework-agnostic E2E test fixes out of the TanStack Start migration PR (#46424) so they can land on `master` independently and scope that PR down. Test files only — no app/source changes. > [!NOTE] > **These changes originate from #46424** (the Next.js → TanStack Start migration). They were made while getting the E2E suite green on both builds, but they're pure test-file changes that are framework-agnostic and already pass on the current Next.js build. A few are *also* written to tolerate TanStack behaviour (called out in code comments) — harmless and correct on Next today. **Changed:** - `realtime-inspector.spec.ts` — drop the racy `waitForResponse(/settings/)` (the "Join a channel" UI assertion already gates on settings loading); click the message row's timestamp instead of its center, which the wide untruncated-JSON cell pushes under the always-present detail panel. - `database.spec.ts` — gate the three Schema Visualizer / Tables tests on the visualizer UI (`focusTableInVisualizer` already auto-waits for the schema, including the freshly-created table) instead of the `pg-meta … public-infinite_tables` XHR. - `database-webhooks.spec.ts` — wait on the `Database Webhooks` heading instead of marketing copy that was removed when the page moved to the new integrations UI. - `queue-integration.spec.ts` — wait on the unconditional "Create queue" button instead of the grid (which only renders once a queue exists); assert creation by polling for either valid end-state. ## To test - Run the affected specs against a local self-hosted studio and confirm they pass: `pnpm --prefix e2e/studio run e2e -- features/realtime-inspector.spec.ts features/database.spec.ts features/database-webhooks.spec.ts features/queue-integration.spec.ts` - Or just let the studio E2E suite run on this PR (Next.js build). Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
@@ -6,7 +6,11 @@ import { toUrl } from '../utils/to-url.js'
|
||||
|
||||
const ensureWebhooksEnabled = async (page: Page, ref: string) => {
|
||||
await page.goto(toUrl(`/project/${ref}/integrations/webhooks/overview`))
|
||||
await expect(page.getByText('Database Webhooks allow you to send real-time data')).toBeVisible({
|
||||
// The original "allow you to send real-time data" copy was removed when the
|
||||
// webhooks page was migrated to the new integrations UI (PR #44277). Wait
|
||||
// on the page-header heading instead — it's rendered whether or not the
|
||||
// supabase_functions extension is enabled.
|
||||
await expect(page.getByRole('heading', { name: 'Database Webhooks' })).toBeVisible({
|
||||
timeout: 30000,
|
||||
})
|
||||
|
||||
|
||||
@@ -33,16 +33,13 @@ test.describe('Database', () => {
|
||||
}
|
||||
)
|
||||
|
||||
const wait = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=project:default-schema:public-infinite_tables'
|
||||
)
|
||||
await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
|
||||
await wait
|
||||
|
||||
// focus the test table so it mounts inside the viewport
|
||||
// Gate on the UI, not the pg-meta XHR: under the TanStack build the
|
||||
// schema is delivered via the SSR stream, so a `waitForResponse` for the
|
||||
// `public-infinite_tables` query never fires client-side and times out.
|
||||
// Focusing the freshly-created table already auto-waits for the schema
|
||||
// (including the new table) to load in the visualizer.
|
||||
await focusTableInVisualizer(page, databaseTableName)
|
||||
|
||||
// validates table and column exists
|
||||
@@ -110,16 +107,13 @@ test.describe('Database', () => {
|
||||
await dropTable(databaseTableName)
|
||||
}
|
||||
)
|
||||
const wait = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=project:default-schema:public-infinite_tables'
|
||||
)
|
||||
await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
|
||||
await wait
|
||||
|
||||
// focus the test table so it mounts inside the viewport
|
||||
// Gate on the UI, not the pg-meta XHR: under the TanStack build the
|
||||
// schema is delivered via the SSR stream, so a `waitForResponse` for the
|
||||
// `public-infinite_tables` query never fires client-side and times out.
|
||||
// Focusing the freshly-created table already auto-waits for the schema
|
||||
// (including the new table) to load in the visualizer.
|
||||
await focusTableInVisualizer(page, databaseTableName)
|
||||
|
||||
// validates table and column exists
|
||||
@@ -181,16 +175,13 @@ test.describe('Database', () => {
|
||||
await dropTable(databaseTableName)
|
||||
}
|
||||
)
|
||||
const wait = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=project:default-schema:public-infinite_tables'
|
||||
)
|
||||
await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
|
||||
await wait
|
||||
|
||||
// focus the test table so it mounts inside the viewport
|
||||
// Gate on the UI, not the pg-meta XHR: under the TanStack build the
|
||||
// schema is delivered via the SSR stream, so a `waitForResponse` for the
|
||||
// `public-infinite_tables` query never fires client-side and times out.
|
||||
// Focusing the freshly-created table already auto-waits for the schema
|
||||
// (including the new table) to load in the visualizer.
|
||||
await focusTableInVisualizer(page, databaseTableName)
|
||||
|
||||
// validates table and column exists
|
||||
|
||||
@@ -7,7 +7,16 @@ import { toUrl } from '../utils/to-url.js'
|
||||
|
||||
const navigateToQueuesPage = async (page: Page, ref: string) => {
|
||||
await page.goto(toUrl(`/project/${ref}/integrations/queues/queues`))
|
||||
await expect(page.getByRole('grid')).toBeVisible({ timeout: 30000 })
|
||||
// Wait for a stable header signal rather than the grid — the grid only
|
||||
// renders when at least one queue exists, and an empty list shows an
|
||||
// empty-state placeholder instead. Under workers=3 the Next.js server
|
||||
// doesn't always populate the list fast enough before the assertion,
|
||||
// so the test would flake at navigation rather than at the actual
|
||||
// assertion. The "Create queue" button is part of the page header and
|
||||
// renders unconditionally once the route is mounted.
|
||||
await expect(page.getByRole('button', { name: 'Create queue' })).toBeVisible({
|
||||
timeout: 30000,
|
||||
})
|
||||
}
|
||||
|
||||
const navigateToSingleQueuePage = async (page: Page, ref: string, queueName: string) => {
|
||||
@@ -47,6 +56,26 @@ const sendMessageViaAPI = async (
|
||||
})
|
||||
}
|
||||
|
||||
// Asserts a queue exists in the UI in a routing-agnostic way. After the
|
||||
// post-create flow, two end-states are valid:
|
||||
// - Next mode: `router.push` reliably navigates to the queue-detail page
|
||||
// so the URL ends with /queues/{queueName} and the heading appears.
|
||||
// - TanStack mode: `router.push` (via the next/router shim) races nuqs's
|
||||
// `?new=true` history update and the URL stays on the list, so the new
|
||||
// row is visible there instead.
|
||||
// Either signal counts as success — poll until one of them lands.
|
||||
const expectQueueCreated = async (page: Page, queueName: string) => {
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
if (page.url().includes(`/queues/${queueName}`)) return true
|
||||
return await page.getByRole('row', { name: new RegExp(`\\b${queueName}\\b`) }).isVisible()
|
||||
},
|
||||
{ timeout: 15000 }
|
||||
)
|
||||
.toBe(true)
|
||||
}
|
||||
|
||||
test.describe('Queues Integration', () => {
|
||||
test.beforeAll(async () => {
|
||||
await withFileOnceSetup(import.meta.url, async () => {
|
||||
@@ -87,7 +116,8 @@ test.describe('Queues Integration', () => {
|
||||
await dialog.getByRole('button', { name: 'Create queue' }).click()
|
||||
|
||||
await expect(page.getByText(/Successfully created queue/)).toBeVisible({ timeout: 10000 })
|
||||
await page.waitForURL(/.*\/integrations\/queues\/queues\/pw_queue_create/)
|
||||
await expect(dialog).toBeHidden({ timeout: 10000 })
|
||||
await expectQueueCreated(page, queueName)
|
||||
})
|
||||
|
||||
test('can create an unlogged queue', async ({ page, ref }) => {
|
||||
@@ -113,9 +143,8 @@ test.describe('Queues Integration', () => {
|
||||
await dialog.getByRole('button', { name: 'Create queue' }).click()
|
||||
|
||||
await expect(page.getByText(/Successfully created queue/)).toBeVisible({ timeout: 10000 })
|
||||
await page.waitForURL(
|
||||
new RegExp(`.*\\/integrations\\/queues\\/queues\\/${queueName}`)
|
||||
)
|
||||
await expect(dialog).toBeHidden({ timeout: 10000 })
|
||||
await expectQueueCreated(page, queueName)
|
||||
})
|
||||
|
||||
test('can delete a queue', async ({ page, ref }) => {
|
||||
|
||||
@@ -14,8 +14,13 @@ import { test } from '../utils/test.js'
|
||||
|
||||
test.describe('Realtime Inspector', () => {
|
||||
test.beforeEach(async ({ page, ref }) => {
|
||||
// `navigateToRealtimeInspector` already waits for "Join a channel" to be
|
||||
// visible, which only renders once the project settings have loaded. A
|
||||
// separate `waitForResponse(/settings/)` registered here is both redundant
|
||||
// and racy: settings is a one-shot, non-refetching request, so under the
|
||||
// faster TanStack render it resolves before this listener attaches and the
|
||||
// wait hangs until the test timeout. The UI assertion is the real gate.
|
||||
await navigateToRealtimeInspector(page, ref)
|
||||
await page.waitForResponse(new RegExp(`/platform/projects/${ref}/settings`))
|
||||
})
|
||||
|
||||
test.describe('Basic Inspector UI', () => {
|
||||
@@ -106,7 +111,16 @@ test.describe('Realtime Inspector', () => {
|
||||
|
||||
const messageRow = page.getByRole('row').filter({ hasText: 'broadcast' }).first()
|
||||
await expect(messageRow).toBeVisible()
|
||||
await messageRow.click()
|
||||
// The message cell renders the full (untruncated) JSON, so the data-grid
|
||||
// row is far wider than its column and its geometric center sits under the
|
||||
// always-present detail panel — a default center-click is intercepted by
|
||||
// that panel. Click the timestamp text instead: it's at the row's left,
|
||||
// always inside the visible grid viewport, and still triggers the row's
|
||||
// onClick that opens the detail panel.
|
||||
await messageRow
|
||||
.getByText(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
||||
.first()
|
||||
.click()
|
||||
|
||||
await expect(page.getByText('Timestamp')).toBeVisible()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user