mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
test(studio): add tests for data api toggles (#42051)
* test(studio): add tests for data api toggles
This commit is contained in:
1 parent
d34cc65e30
commit
d27bb74258
5 files changed
+603
-1
No files matched your search
@@ -0,0 +1,545 @@
|
||||
import { expect, Page } from '@playwright/test'
|
||||
|
||||
import { test } from '../utils/test.js'
|
||||
import { toUrl } from '../utils/to-url.js'
|
||||
import {
|
||||
createApiResponseWaiter,
|
||||
waitForApiResponse,
|
||||
waitForTableToLoad,
|
||||
} from '../utils/wait-for-response.js'
|
||||
import { dismissToastsIfAny } from '../utils/dismiss-toast.js'
|
||||
import { openTableContextMenu, deleteTable } from '../utils/table-helpers.js'
|
||||
|
||||
const TABLE_NAME_PREFIX = 'pw_api_access'
|
||||
|
||||
/**
|
||||
* The API privilege types we care about for Data API access.
|
||||
* Filters out other PostgreSQL privileges like REFERENCES, TRIGGER, TRUNCATE.
|
||||
*/
|
||||
const API_PRIVILEGE_TYPES = ['SELECT', 'INSERT', 'UPDATE', 'DELETE']
|
||||
|
||||
/**
|
||||
* Executes a SQL query via the SQL Editor and returns the result.
|
||||
* This is used to verify database state directly.
|
||||
*/
|
||||
async function executeSql(
|
||||
page: Page,
|
||||
ref: string,
|
||||
sql: string
|
||||
): Promise<Array<Record<string, unknown>>> {
|
||||
// Create API response waiter for content/count which indicates project is loaded
|
||||
const contentCountWaiter = createApiResponseWaiter(
|
||||
page,
|
||||
'platform/projects',
|
||||
ref,
|
||||
'content/count'
|
||||
)
|
||||
|
||||
// Navigate to SQL editor
|
||||
await page.goto(toUrl(`/project/${ref}/sql/new?skip=true`))
|
||||
|
||||
// Wait for content/count API response to ensure project is fully loaded
|
||||
await contentCountWaiter
|
||||
|
||||
await expect(page.getByText('Loading...')).not.toBeVisible({ timeout: 10000 })
|
||||
|
||||
// Clear and type the SQL
|
||||
await page.locator('.view-lines').click()
|
||||
await page.keyboard.press('ControlOrMeta+KeyA')
|
||||
await page.keyboard.type(sql)
|
||||
|
||||
// Run the query
|
||||
const sqlMutationPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await page.getByTestId('sql-run-button').click()
|
||||
await sqlMutationPromise
|
||||
|
||||
// Wait for either results grid or "Success. No rows returned" message
|
||||
const grid = page.getByRole('grid')
|
||||
const noRowsMessage = page.getByText('Success. No rows returned')
|
||||
|
||||
// Wait for either element to appear
|
||||
await expect(grid.or(noRowsMessage)).toBeVisible({ timeout: 10000 })
|
||||
|
||||
// If no rows returned, return empty array
|
||||
if (await noRowsMessage.isVisible().catch(() => false)) {
|
||||
return []
|
||||
}
|
||||
|
||||
// If grid is not visible (shouldn't happen if we get here, but just in case)
|
||||
if ((await grid.count()) === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Extract column headers
|
||||
const headers: Array<string> = []
|
||||
const headerCells = grid.getByRole('columnheader')
|
||||
const headerCount = await headerCells.count()
|
||||
for (let i = 0; i < headerCount; i++) {
|
||||
const text = await headerCells.nth(i).textContent()
|
||||
if (text) headers.push(text.trim())
|
||||
}
|
||||
|
||||
// Extract row data
|
||||
const results: Array<Record<string, unknown>> = []
|
||||
const rows = grid.getByRole('row')
|
||||
const rowCount = await rows.count()
|
||||
|
||||
// Skip first row (header row)
|
||||
for (let i = 1; i < rowCount; i++) {
|
||||
const cells = rows.nth(i).getByRole('gridcell')
|
||||
const cellCount = await cells.count()
|
||||
const row: Record<string, unknown> = {}
|
||||
|
||||
for (let j = 0; j < Math.min(cellCount, headers.length); j++) {
|
||||
const cellText = await cells.nth(j).textContent()
|
||||
row[headers[j]] = cellText?.trim() ?? null
|
||||
}
|
||||
|
||||
if (Object.keys(row).length > 0) {
|
||||
results.push(row)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
async function verifyTablePrivileges(
|
||||
page: Page,
|
||||
ref: string,
|
||||
schemaName: string,
|
||||
tableName: string,
|
||||
expectedPrivileges: {
|
||||
anon: Array<string>
|
||||
authenticated: Array<string>
|
||||
}
|
||||
) {
|
||||
const sql = `
|
||||
SELECT grantee, privilege_type
|
||||
FROM information_schema.role_table_grants
|
||||
WHERE table_schema = '${schemaName}'
|
||||
AND table_name = '${tableName}'
|
||||
AND grantee IN ('anon', 'authenticated')
|
||||
AND privilege_type IN ('SELECT', 'INSERT', 'UPDATE', 'DELETE')
|
||||
ORDER BY grantee, privilege_type;
|
||||
`
|
||||
|
||||
const results = await executeSql(page, ref, sql)
|
||||
|
||||
// Group privileges by grantee
|
||||
const actualPrivileges: Record<string, Array<string>> = {
|
||||
anon: [],
|
||||
authenticated: [],
|
||||
}
|
||||
|
||||
for (const row of results) {
|
||||
const grantee = row['grantee'] as string
|
||||
const privilegeType = row['privilege_type'] as string
|
||||
if (
|
||||
grantee &&
|
||||
privilegeType &&
|
||||
(grantee === 'anon' || grantee === 'authenticated') &&
|
||||
API_PRIVILEGE_TYPES.includes(privilegeType)
|
||||
) {
|
||||
actualPrivileges[grantee].push(privilegeType)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort for comparison
|
||||
actualPrivileges.anon.sort()
|
||||
actualPrivileges.authenticated.sort()
|
||||
const sortedExpected = {
|
||||
anon: [...expectedPrivileges.anon].sort(),
|
||||
authenticated: [...expectedPrivileges.authenticated].sort(),
|
||||
}
|
||||
|
||||
expect(actualPrivileges.anon).toEqual(sortedExpected.anon)
|
||||
expect(actualPrivileges.authenticated).toEqual(sortedExpected.authenticated)
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the API access toggle switch for Data API Access.
|
||||
* The switch is labeled by the nearby "Data API Access" text.
|
||||
*/
|
||||
function getApiAccessToggle(page: Page) {
|
||||
const sidePanel = page.getByTestId('table-editor-side-panel')
|
||||
// The switch is near the "Data API Access" label - get the section first, then find the switch
|
||||
const dataApiSection = sidePanel
|
||||
.locator('div')
|
||||
.filter({ hasText: 'Data API Access' })
|
||||
.filter({ has: page.getByRole('switch') })
|
||||
return dataApiSection.getByRole('switch')
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the settings button for granular privilege settings.
|
||||
*/
|
||||
function getPrivilegeSettingsButton(page: Page) {
|
||||
const sidePanel = page.getByTestId('table-editor-side-panel')
|
||||
return sidePanel.getByRole('button', { name: 'Configure API privileges' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the privilege selector combobox for a specific role in the privileges popover.
|
||||
* The popover must already be open.
|
||||
*/
|
||||
function getRolePrivilegeSelector(page: Page, roleLabel: 'Anonymous (anon)' | 'Authenticated') {
|
||||
// The popover is a dialog with structure: paragraph (role label) followed by combobox
|
||||
// We find the paragraph with the role text, then get the adjacent combobox
|
||||
const popoverContent = page.locator('[data-radix-popper-content-wrapper]')
|
||||
// Get the paragraph containing the role label, then navigate to the sibling combobox
|
||||
return popoverContent.getByText(roleLabel, { exact: true }).locator('..').getByRole('combobox')
|
||||
}
|
||||
|
||||
test.describe('API Access Toggle', () => {
|
||||
test.beforeEach(async ({ page, ref }) => {
|
||||
const loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await loadPromise
|
||||
})
|
||||
|
||||
test('API access is default on for a new table', async ({ page, ref }) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_default_on`
|
||||
|
||||
// Open new table dialog
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
|
||||
// Fill in table name
|
||||
await page.getByTestId('table-name-input').fill(tableName)
|
||||
|
||||
// Find and click the API access toggle to turn it off
|
||||
const toggle = getApiAccessToggle(page)
|
||||
await expect(toggle).toBeChecked()
|
||||
|
||||
// Create the table
|
||||
const createTablePromise = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=table-create'
|
||||
)
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await createTablePromise
|
||||
|
||||
// Wait for success toast which indicates all operations are complete
|
||||
await expect(
|
||||
page.getByText(`Table ${tableName} is good to go!`),
|
||||
'Success toast should appear after table creation'
|
||||
).toBeVisible({ timeout: 15000 })
|
||||
|
||||
// Dismiss toast to prevent it from blocking subsequent interactions
|
||||
await dismissToastsIfAny(page)
|
||||
|
||||
await page.waitForSelector('[data-testid="table-editor-side-panel"]', { state: 'detached' })
|
||||
|
||||
// Verify table was created
|
||||
await expect(
|
||||
page.getByRole('button', { name: `View ${tableName}`, exact: true }),
|
||||
'Table should be visible after creation'
|
||||
).toBeVisible()
|
||||
|
||||
// Verify all API access privileges were granted
|
||||
await verifyTablePrivileges(page, ref, 'public', tableName, {
|
||||
anon: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
})
|
||||
})
|
||||
|
||||
test('can toggle API access off for a new table', async ({ page, ref }) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_toggle_off`
|
||||
|
||||
// Open new table dialog
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
|
||||
// Fill in table name
|
||||
await page.getByTestId('table-name-input').fill(tableName)
|
||||
|
||||
// Find and click the API access toggle to turn it off
|
||||
const toggle = getApiAccessToggle(page)
|
||||
await expect(toggle).toBeChecked()
|
||||
await toggle.click()
|
||||
await expect(toggle, 'Toggle should be unchecked after clicking').not.toBeChecked()
|
||||
|
||||
// Create the table
|
||||
const createTablePromise = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=table-create'
|
||||
)
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await createTablePromise
|
||||
|
||||
// Wait for success toast which indicates all operations are complete
|
||||
await expect(
|
||||
page.getByText(`Table ${tableName} is good to go!`),
|
||||
'Success toast should appear after table creation'
|
||||
).toBeVisible({ timeout: 15000 })
|
||||
|
||||
// Dismiss toast to prevent it from blocking subsequent interactions
|
||||
await dismissToastsIfAny(page)
|
||||
|
||||
await page.waitForSelector('[data-testid="table-editor-side-panel"]', { state: 'detached' })
|
||||
|
||||
// Verify table was created
|
||||
await expect(
|
||||
page.getByRole('button', { name: `View ${tableName}`, exact: true }),
|
||||
'Table should be visible after creation'
|
||||
).toBeVisible()
|
||||
|
||||
// Verify no API access privileges were granted
|
||||
await verifyTablePrivileges(page, ref, 'public', tableName, {
|
||||
anon: [],
|
||||
authenticated: [],
|
||||
})
|
||||
})
|
||||
|
||||
test('shows API access toggle when editing an existing table', async ({ page, ref }) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_edit`
|
||||
|
||||
// Create a table first
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
await page.getByTestId('table-name-input').fill(tableName)
|
||||
|
||||
const createPromise = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-create')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await createPromise
|
||||
|
||||
// Wait for success toast which indicates all operations are complete
|
||||
await expect(
|
||||
page.getByText(`Table ${tableName} is good to go!`),
|
||||
'Success toast should appear after table creation'
|
||||
).toBeVisible({ timeout: 15000 })
|
||||
|
||||
// Dismiss toast to prevent it from blocking subsequent interactions
|
||||
await dismissToastsIfAny(page)
|
||||
|
||||
await page.waitForSelector('[data-testid="table-editor-side-panel"]', { state: 'detached' })
|
||||
|
||||
// Verify table was created
|
||||
await expect(
|
||||
page.getByRole('button', { name: `View ${tableName}`, exact: true }),
|
||||
'Table should be visible after creation'
|
||||
).toBeVisible()
|
||||
|
||||
// Verify default full privileges were granted
|
||||
await verifyTablePrivileges(page, ref, 'public', tableName, {
|
||||
anon: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
})
|
||||
|
||||
// Navigate back to table editor
|
||||
let loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await loadPromise
|
||||
|
||||
// Click on the table to view it
|
||||
const navigationPromise = page.waitForURL(/\/editor\/\d+\?schema=public$/)
|
||||
await page.getByRole('button', { name: `View ${tableName}`, exact: true }).click()
|
||||
await navigationPromise
|
||||
|
||||
// Open edit table dialog via context menu
|
||||
await openTableContextMenu(page, tableName)
|
||||
await page.getByRole('menuitem', { name: 'Edit table' }).click()
|
||||
|
||||
// Verify the side panel is open
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
|
||||
// Verify Data API Access section is visible
|
||||
await expect(
|
||||
page.getByText('Data API Access'),
|
||||
'Data API Access label should be visible in edit mode'
|
||||
).toBeVisible()
|
||||
|
||||
// Verify the toggle is present
|
||||
const toggle = getApiAccessToggle(page)
|
||||
await expect(toggle, 'API Access toggle should be visible in edit mode').toBeVisible()
|
||||
})
|
||||
|
||||
test('creates table with partial privileges and verifies correct grants', async ({
|
||||
page,
|
||||
ref,
|
||||
}) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_partial_grants`
|
||||
|
||||
// Open new table dialog
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
|
||||
// Fill in table name
|
||||
await page.getByTestId('table-name-input').fill(tableName)
|
||||
|
||||
// Open the privilege settings popover
|
||||
const settingsButton = getPrivilegeSettingsButton(page)
|
||||
await settingsButton.click()
|
||||
|
||||
await expect(page.getByText('Adjust API privileges per role')).toBeVisible()
|
||||
|
||||
// Modify anon privileges - leave only SELECT
|
||||
const anonSelector = getRolePrivilegeSelector(page, 'Anonymous (anon)')
|
||||
await anonSelector.click()
|
||||
|
||||
// Click DELETE to toggle it off
|
||||
await page.getByRole('option', { name: 'DELETE' }).click()
|
||||
// Click UPDATE to toggle it off
|
||||
await page.getByRole('option', { name: 'UPDATE' }).click()
|
||||
await page.getByRole('option', { name: 'INSERT' }).click()
|
||||
|
||||
// Close the dropdown by clicking the combobox again
|
||||
await anonSelector.click()
|
||||
|
||||
// Wait for dropdown to close
|
||||
await expect(page.getByRole('option', { name: 'DELETE' })).not.toBeVisible({ timeout: 2000 })
|
||||
|
||||
// Modify authenticated privileges - remove DELETE and UPDATE (leave SELECT + INSERT)
|
||||
const authSelector = getRolePrivilegeSelector(page, 'Authenticated')
|
||||
await authSelector.click()
|
||||
|
||||
// Remove all except SELECT
|
||||
await page.getByRole('option', { name: 'DELETE' }).click()
|
||||
await page.getByRole('option', { name: 'UPDATE' }).click()
|
||||
|
||||
// Close the dropdown by clicking the combobox again
|
||||
await authSelector.click()
|
||||
|
||||
// Wait for dropdown to close
|
||||
await expect(page.getByRole('option', { name: 'DELETE' })).not.toBeVisible({ timeout: 2000 })
|
||||
|
||||
// Close the popover by pressing Escape
|
||||
await page.keyboard.press('Escape')
|
||||
|
||||
// Create the table
|
||||
const createTablePromise = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=table-create'
|
||||
)
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await createTablePromise
|
||||
|
||||
// Wait for success toast which indicates all operations (including privilege updates) are complete
|
||||
await expect(
|
||||
page.getByText(`Table ${tableName} is good to go!`),
|
||||
'Success toast should appear after table creation'
|
||||
).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await page.waitForSelector('[data-testid="table-editor-side-panel"]', { state: 'detached' })
|
||||
|
||||
// Verify table was created
|
||||
await expect(
|
||||
page.getByRole('button', { name: `View ${tableName}`, exact: true }),
|
||||
'Table should be visible after creation'
|
||||
).toBeVisible()
|
||||
|
||||
// Verify partial grants - anon: SELECT; authenticated: SELECT, INSERT
|
||||
await verifyTablePrivileges(page, ref, 'public', tableName, {
|
||||
anon: ['SELECT'],
|
||||
authenticated: ['SELECT', 'INSERT'],
|
||||
})
|
||||
})
|
||||
|
||||
test('preserves API grants when editing non-privilege table properties', async ({
|
||||
page,
|
||||
ref,
|
||||
}) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_preserve_grants`
|
||||
|
||||
// Step 1: Create a table with partial privileges (only SELECT and INSERT for anon)
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
|
||||
await page.getByTestId('table-name-input').fill(tableName)
|
||||
|
||||
// Open privilege settings and set partial privileges
|
||||
const settingsButton = getPrivilegeSettingsButton(page)
|
||||
await settingsButton.click()
|
||||
await expect(page.getByText('Adjust API privileges per role')).toBeVisible()
|
||||
|
||||
// Modify anon privileges - keep only SELECT and INSERT
|
||||
const anonSelector = getRolePrivilegeSelector(page, 'Anonymous (anon)')
|
||||
await anonSelector.click()
|
||||
await page.getByRole('option', { name: 'DELETE' }).click()
|
||||
await page.getByRole('option', { name: 'UPDATE' }).click()
|
||||
await anonSelector.click()
|
||||
await expect(page.getByRole('option', { name: 'DELETE' })).not.toBeVisible({ timeout: 2000 })
|
||||
|
||||
// Keep authenticated with full privileges
|
||||
await page.keyboard.press('Escape') // Close popover
|
||||
|
||||
// Create the table
|
||||
let createPromise = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-create')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await createPromise
|
||||
|
||||
// Wait for success toast which indicates all operations (including privilege updates) are complete
|
||||
await expect(
|
||||
page.getByText(`Table ${tableName} is good to go!`),
|
||||
'Success toast should appear after table creation'
|
||||
).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await page.waitForSelector('[data-testid="table-editor-side-panel"]', { state: 'detached' })
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: `View ${tableName}`, exact: true }),
|
||||
'Table should be created'
|
||||
).toBeVisible()
|
||||
|
||||
// Verify initial privileges before edit
|
||||
await verifyTablePrivileges(page, ref, 'public', tableName, {
|
||||
anon: ['SELECT', 'INSERT'],
|
||||
authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
})
|
||||
|
||||
// Navigate back to table editor
|
||||
let loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await loadPromise
|
||||
|
||||
// Step 2: Edit the table's description (without touching privileges)
|
||||
await page.getByRole('button', { name: `View ${tableName}`, exact: true }).click()
|
||||
await page.waitForURL(/\/editor\/\d+\?schema=public$/)
|
||||
|
||||
await openTableContextMenu(page, tableName)
|
||||
await page.getByRole('menuitem', { name: 'Edit table' }).click()
|
||||
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
|
||||
// Add a description without modifying privileges
|
||||
const descriptionInput = page
|
||||
.getByTestId('table-editor-side-panel')
|
||||
.getByPlaceholder('Optional')
|
||||
await descriptionInput.fill('Test description for grant preservation')
|
||||
|
||||
// Save the changes
|
||||
const updatePromise = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-update')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await updatePromise
|
||||
|
||||
// Wait for success toast which indicates all operations are complete
|
||||
await expect(
|
||||
page.getByText(`Successfully updated ${tableName}!`),
|
||||
'Success toast should appear after table update'
|
||||
).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await page.waitForSelector('[data-testid="table-editor-side-panel"]', { state: 'detached' })
|
||||
|
||||
// Step 3: Verify the privileges remain unchanged after edit
|
||||
await verifyTablePrivileges(page, ref, 'public', tableName, {
|
||||
anon: ['SELECT', 'INSERT'],
|
||||
authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
})
|
||||
|
||||
// Navigate back to table editor for cleanup
|
||||
loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await loadPromise
|
||||
|
||||
// Clean up
|
||||
await deleteTable(page, ref, tableName)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import { expect, Page } from '@playwright/test'
|
||||
import { waitForApiResponse } from './wait-for-response.js'
|
||||
|
||||
/**
|
||||
* Opens the context menu for a table in the sidebar.
|
||||
* Hovers over the table button first to reveal the menu button, then clicks it.
|
||||
*/
|
||||
export async function openTableContextMenu(page: Page, tableName: string) {
|
||||
const tableButton = page.getByRole('button', { name: `View ${tableName}`, exact: true })
|
||||
await tableButton.hover()
|
||||
// The menu button appears on hover - wait for it to be visible before clicking
|
||||
const menuButton = tableButton.locator('button[aria-haspopup="menu"]')
|
||||
await expect(menuButton).toBeVisible()
|
||||
await menuButton.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a table using its context menu.
|
||||
* Checks if the table exists first, navigates to it, and then deletes it with cascade.
|
||||
*/
|
||||
export async function deleteTable(page: Page, ref: string, tableName: string) {
|
||||
const viewLocator = page.getByRole('button', { name: `View ${tableName}`, exact: true })
|
||||
if ((await viewLocator.count()) === 0) return
|
||||
await viewLocator.click()
|
||||
await openTableContextMenu(page, tableName)
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Delete table' }).click()
|
||||
await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).click()
|
||||
const apiPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-delete-', {
|
||||
method: 'POST',
|
||||
})
|
||||
const revalidatePromise = waitForApiResponse(page, 'pg-meta', ref, `query?key=entity-types-`)
|
||||
await page.getByRole('button', { name: 'Delete' }).click()
|
||||
await Promise.all([apiPromise, revalidatePromise])
|
||||
|
||||
await expect(page.getByTestId('confirm-delete-table-modal')).not.toBeVisible()
|
||||
}
|
||||
Reference in new issue
Block a user