mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 11:55:05 +03:00
`ensureWebhooksEnabled` was timing out on `Database Webhooks allow you to send real-time data` — copy that was removed when PR #44277 migrated the webhooks page to the new integrations UI. The new page only renders "Database Webhooks can be used to trigger serverless functions..." inside an Admonition that conditionally appears (and disappears once the supabase_functions extension is enabled), so it can't be a stable wait. Wait on the page-header heading instead — it's rendered regardless of the extension state. All 5 specs in `database-webhooks.spec.ts` pass after this change.
226 lines
8.8 KiB
TypeScript
226 lines
8.8 KiB
TypeScript
import { expect, Page } from '@playwright/test'
|
|
|
|
import { createTable, dropTable } from '../utils/db/index.js'
|
|
import { test } from '../utils/test.js'
|
|
import { toUrl } from '../utils/to-url.js'
|
|
|
|
const ensureWebhooksEnabled = async (page: Page, ref: string) => {
|
|
await page.goto(toUrl(`/project/${ref}/integrations/webhooks/overview`))
|
|
// 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,
|
|
})
|
|
|
|
const enableButton = page.getByRole('button', { name: 'Enable webhooks' })
|
|
if ((await enableButton.count()) > 0) {
|
|
await enableButton.click()
|
|
await expect(page.getByText('Successfully enabled webhooks')).toBeVisible({ timeout: 15000 })
|
|
}
|
|
}
|
|
|
|
const navigateToWebhooksList = async (page: Page, ref: string) => {
|
|
await page.goto(toUrl(`/project/${ref}/integrations/webhooks/webhooks`))
|
|
await expect(page.getByPlaceholder('Search for a webhook')).toBeVisible({ timeout: 30000 })
|
|
}
|
|
|
|
const createWebhookViaUI = async (page: Page, hookName: string, tableName: string) => {
|
|
await page.getByRole('button', { name: 'Create a new hook' }).click()
|
|
await expect(page.getByText('Create a new database webhook')).toBeVisible({ timeout: 10000 })
|
|
|
|
await page.locator('input[name="name"]').fill(hookName)
|
|
|
|
await page.getByRole('combobox').filter({ hasText: 'Select a table' }).click()
|
|
await page.getByRole('option', { name: new RegExp(tableName) }).click()
|
|
|
|
await page.locator('#event-INSERT').click()
|
|
|
|
await page
|
|
.getByPlaceholder('http://api.com/path/resource')
|
|
.fill('http://localhost:3000/test-webhook')
|
|
|
|
await page.getByRole('button', { name: 'Create webhook' }).click()
|
|
await expect(page.getByText(`Successfully created new webhook "${hookName}"`)).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
}
|
|
|
|
const openWebhookEditor = async (page: Page, hookName: string) => {
|
|
const webhookText = page.getByText(hookName, { exact: true })
|
|
const webhookRow = page.locator('tr').filter({ has: webhookText })
|
|
await webhookRow.getByRole('button').click()
|
|
await page.getByRole('menuitem', { name: 'Edit hook' }).click()
|
|
await expect(page.getByText(`Update webhook ${hookName}`)).toBeVisible({ timeout: 10000 })
|
|
}
|
|
|
|
const addCustomHeader = async (page: Page, name: string, value: string) => {
|
|
await page.getByRole('button', { name: 'Add a new header' }).click()
|
|
await page.getByPlaceholder('Header name').last().fill(name)
|
|
await page.getByPlaceholder('Header value').last().fill(value)
|
|
}
|
|
|
|
const deleteWebhookViaUI = async (page: Page, name: string) => {
|
|
const webhookText = page.getByText(name, { exact: true })
|
|
const webhookRow = page.locator('tr').filter({ has: webhookText })
|
|
await webhookRow.getByRole('button').click()
|
|
|
|
await page.getByRole('menuitem', { name: 'Delete hook' }).click()
|
|
|
|
await expect(page.getByRole('heading', { name: 'Delete database webhook' })).toBeVisible()
|
|
await page.getByPlaceholder('Type in name of webhook').fill(name)
|
|
await page.getByRole('button', { name: `Delete ${name}` }).click()
|
|
|
|
await expect(page.getByText(`Successfully deleted ${name}`)).toBeVisible({ timeout: 10000 })
|
|
await expect(webhookRow).not.toBeVisible({ timeout: 10000 })
|
|
}
|
|
|
|
const setupTable = async (tableName: string) => {
|
|
await dropTable(tableName)
|
|
await createTable(tableName, 'data')
|
|
}
|
|
|
|
const uniqueNames = (prefix: string) => {
|
|
const i = test.info().parallelIndex
|
|
return { table: `pw_wh_${prefix}_tbl_${i}`, hook: `pw_wh_${prefix}_${i}` }
|
|
}
|
|
|
|
test.describe('Database Webhooks', () => {
|
|
test('can view webhooks list page', async ({ page, ref }) => {
|
|
await ensureWebhooksEnabled(page, ref)
|
|
await navigateToWebhooksList(page, ref)
|
|
|
|
await expect(page.getByPlaceholder('Search for a webhook')).toBeVisible()
|
|
await expect(page.getByRole('button', { name: 'Create a new hook' })).toBeVisible()
|
|
})
|
|
|
|
test('can create a new webhook with correct toast message', async ({ page, ref }) => {
|
|
const { table, hook } = uniqueNames('create')
|
|
await setupTable(table)
|
|
|
|
try {
|
|
await ensureWebhooksEnabled(page, ref)
|
|
await navigateToWebhooksList(page, ref)
|
|
|
|
await page.getByRole('button', { name: 'Create a new hook' }).click()
|
|
await expect(page.getByText('Create a new database webhook')).toBeVisible({ timeout: 10000 })
|
|
|
|
await page.locator('input[name="name"]').fill(hook)
|
|
await page.getByRole('combobox').filter({ hasText: 'Select a table' }).click()
|
|
await page.getByRole('option', { name: new RegExp(table) }).click()
|
|
await page.locator('#event-INSERT').click()
|
|
await page
|
|
.getByPlaceholder('http://api.com/path/resource')
|
|
.fill('http://localhost:3000/test-webhook')
|
|
|
|
await page.getByRole('button', { name: 'Create webhook' }).click()
|
|
|
|
await expect(page.getByText(`Successfully created new webhook "${hook}"`)).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
await expect(page.getByText(hook, { exact: true })).toBeVisible({ timeout: 10000 })
|
|
} finally {
|
|
await dropTable(table)
|
|
}
|
|
})
|
|
|
|
test('can edit a webhook with correct toast message', async ({ page, ref }) => {
|
|
const { table, hook } = uniqueNames('edit')
|
|
await setupTable(table)
|
|
|
|
try {
|
|
await ensureWebhooksEnabled(page, ref)
|
|
await navigateToWebhooksList(page, ref)
|
|
await createWebhookViaUI(page, hook, table)
|
|
|
|
const webhookText = page.getByText(hook, { exact: true })
|
|
const webhookRow = page.locator('tr').filter({ has: webhookText })
|
|
await webhookRow.getByRole('button').click()
|
|
|
|
await page.getByRole('menuitem', { name: 'Edit hook' }).click()
|
|
|
|
await expect(page.getByText(`Update webhook ${hook}`)).toBeVisible({ timeout: 10000 })
|
|
|
|
const urlInput = page.getByPlaceholder('http://api.com/path/resource')
|
|
await urlInput.clear()
|
|
await urlInput.fill('http://localhost:3000/test-webhook-updated')
|
|
|
|
await page.getByRole('button', { name: 'Update webhook' }).click()
|
|
|
|
await expect(page.getByText(`Successfully updated webhook "${hook}"`)).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
await expect(page.getByText('Webhook not found')).not.toBeVisible()
|
|
} finally {
|
|
await dropTable(table)
|
|
}
|
|
})
|
|
|
|
test('preserves webhook URL path and custom headers after editing', async ({ page, ref }) => {
|
|
const { table, hook } = uniqueNames('edit_persist')
|
|
const originalUrl = 'http://localhost:3000/test-webhook'
|
|
const updatedUrl = 'http://localhost:3000/test-webhook-updated/path'
|
|
const headerName = 'X-API-Key'
|
|
const headerValue = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12'
|
|
|
|
await setupTable(table)
|
|
|
|
try {
|
|
await ensureWebhooksEnabled(page, ref)
|
|
await navigateToWebhooksList(page, ref)
|
|
await createWebhookViaUI(page, hook, table)
|
|
|
|
await openWebhookEditor(page, hook)
|
|
await addCustomHeader(page, headerName, headerValue)
|
|
await page.getByRole('button', { name: 'Update webhook' }).click()
|
|
|
|
await expect(page.getByText(`Successfully updated webhook "${hook}"`)).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
await openWebhookEditor(page, hook)
|
|
await expect(page.getByPlaceholder('http://api.com/path/resource')).toHaveValue(originalUrl)
|
|
await expect(page.getByPlaceholder('Header name').last()).toHaveValue(headerName)
|
|
await expect(page.getByPlaceholder('Header value').last()).toHaveValue(headerValue)
|
|
|
|
const urlInput = page.getByPlaceholder('http://api.com/path/resource')
|
|
await urlInput.clear()
|
|
await urlInput.fill(updatedUrl)
|
|
await page.getByRole('button', { name: 'Update webhook' }).click()
|
|
|
|
await expect(page.getByText(`Successfully updated webhook "${hook}"`)).toBeVisible({
|
|
timeout: 10000,
|
|
})
|
|
|
|
await openWebhookEditor(page, hook)
|
|
await expect(page.getByPlaceholder('http://api.com/path/resource')).toHaveValue(updatedUrl)
|
|
await expect(page.getByPlaceholder('Header name').last()).toHaveValue(headerName)
|
|
await expect(page.getByPlaceholder('Header value').last()).toHaveValue(headerValue)
|
|
} finally {
|
|
await dropTable(table)
|
|
}
|
|
})
|
|
|
|
test('can delete a webhook with correct toast message', async ({ page, ref }) => {
|
|
const { table, hook } = uniqueNames('delete')
|
|
await setupTable(table)
|
|
|
|
try {
|
|
await ensureWebhooksEnabled(page, ref)
|
|
await navigateToWebhooksList(page, ref)
|
|
await createWebhookViaUI(page, hook, table)
|
|
|
|
await deleteWebhookViaUI(page, hook)
|
|
|
|
await expect(page.getByText(`Successfully deleted ${hook}`)).toBeVisible({ timeout: 10000 })
|
|
|
|
await expect(page.getByText(hook, { exact: true })).not.toBeVisible()
|
|
} finally {
|
|
await dropTable(table)
|
|
}
|
|
})
|
|
})
|