mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore - stabilize and make e2e tests faster - part 2 (#43560)
## Problem - Some tests don't always cleanup after themselves or don't do it consistently which make it hard to work on them locally - Some test suites don't allow parallel execution of their tests - Some tests are flaky This a follow up of #43547
This commit is contained in:
1 parent
7b8bec544e
commit
d004d13e3f
5 files changed
+241
-203
No files matched your search
@@ -1,11 +1,11 @@
|
||||
import { expect, Page } from '@playwright/test'
|
||||
|
||||
import { query } from '../utils/db/index.js'
|
||||
import { test } from '../utils/test.js'
|
||||
import { dropTable, query } from '../utils/db/index.js'
|
||||
import { dismissToastsIfAny } from '../utils/dismiss-toast.js'
|
||||
import { openTableContextMenu } from '../utils/table-helpers.js'
|
||||
import { test, withSetupCleanup } from '../utils/test.js'
|
||||
import { toUrl } from '../utils/to-url.js'
|
||||
import { createApiResponseWaiter, 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'
|
||||
|
||||
@@ -87,7 +87,14 @@ test.describe('API Access Toggle', () => {
|
||||
|
||||
test('API access is default on for a new table', async ({ page, ref }) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_default_on`
|
||||
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
// Nothing
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
// Open new table dialog
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
@@ -135,6 +142,14 @@ test.describe('API Access Toggle', () => {
|
||||
|
||||
test('can toggle API access off for a new table', async ({ page, ref }) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_toggle_off`
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
// Nothing
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
|
||||
// Open new table dialog
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
@@ -185,7 +200,14 @@ test.describe('API Access Toggle', () => {
|
||||
|
||||
test('shows Manage access link when editing an existing table', async ({ page, ref }) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_edit`
|
||||
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
// Nothing
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
// Create a table first
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
@@ -236,7 +258,14 @@ test.describe('API Access Toggle', () => {
|
||||
ref,
|
||||
}) => {
|
||||
const tableName = `${TABLE_NAME_PREFIX}_preserve_grants`
|
||||
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
// Nothing
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
// Step 1: Create a table with API access on (default — full grants)
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
|
||||
@@ -297,12 +326,5 @@ test.describe('API Access Toggle', () => {
|
||||
anon: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||||
})
|
||||
|
||||
// Clean up
|
||||
loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await loadPromise
|
||||
|
||||
await deleteTable(page, ref, tableName)
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,7 @@
|
||||
import { expect, Page } from '@playwright/test'
|
||||
|
||||
import { createTable, dropTable } from '../utils/db/index.js'
|
||||
import { releaseFileOnceCleanup, withFileOnceSetup } from '../utils/once-per-file.js'
|
||||
import { deleteTable } from '../utils/table-helpers.js'
|
||||
import { test } from '../utils/test.js'
|
||||
import { test, withSetupCleanup } from '../utils/test.js'
|
||||
import { toUrl } from '../utils/to-url.js'
|
||||
import { waitForTableToLoad } from '../utils/wait-for-response.js'
|
||||
|
||||
@@ -16,38 +15,6 @@ const enableQueueOperations = async (page: Page) => {
|
||||
}
|
||||
|
||||
test.describe('Queue Table Operations', () => {
|
||||
test.beforeAll(async ({ browser, ref }) => {
|
||||
await withFileOnceSetup(import.meta.url, async () => {
|
||||
const ctx = await browser.newContext()
|
||||
const page = await ctx.newPage()
|
||||
|
||||
const loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await loadPromise
|
||||
|
||||
const viewButtons = page.getByRole('button', { name: /^View / })
|
||||
const names = await Promise.all(
|
||||
(await viewButtons.all()).map(async (btn) => {
|
||||
const ariaLabel = await btn.getAttribute('aria-label')
|
||||
const name = ariaLabel ? ariaLabel.replace(/^View\s+/, '').trim() : ''
|
||||
return name
|
||||
})
|
||||
)
|
||||
const tablesToDelete = names.filter((tableName) => tableName.startsWith(tableNamePrefix))
|
||||
|
||||
for (const tableName of tablesToDelete) {
|
||||
await deleteTable(page, ref, tableName)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.getByLabel(`View ${tableName}`, { exact: true }).count()
|
||||
})
|
||||
.toBe(0)
|
||||
}
|
||||
|
||||
await ctx.close()
|
||||
})
|
||||
})
|
||||
|
||||
test.beforeEach(async ({ page, ref }) => {
|
||||
const loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
@@ -56,15 +23,18 @@ test.describe('Queue Table Operations', () => {
|
||||
await page.reload({ waitUntil: 'networkidle' })
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await releaseFileOnceCleanup(import.meta.url)
|
||||
})
|
||||
|
||||
test('cell edits are queued and can be saved', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_cell_edit`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName, [{ name: 'original value' }])
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName, [{ name: 'original value' }])
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
@@ -99,15 +69,20 @@ test.describe('Queue Table Operations', () => {
|
||||
|
||||
await expect(page.getByRole('gridcell', { name: 'edited value' })).toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'original value' })).not.toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('cell edits can be cancelled', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_cell_cancel`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName, [{ name: 'keep this value' }])
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName, [{ name: 'keep this value' }])
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
@@ -134,15 +109,20 @@ test.describe('Queue Table Operations', () => {
|
||||
await expect(page.getByRole('gridcell', { name: 'should be cancelled' })).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText('pending change')).not.toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('row inserts are queued and can be saved', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_row_insert`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName)
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName)
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
@@ -172,16 +152,20 @@ test.describe('Queue Table Operations', () => {
|
||||
await expect(page.getByText('Changes saved successfully')).toBeVisible()
|
||||
|
||||
await expect(page.getByRole('gridcell', { name: 'new row value' })).toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('multiple operations can be batched and saved together', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_batch_ops`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName)
|
||||
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName)
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
await page.reload()
|
||||
@@ -213,16 +197,20 @@ test.describe('Queue Table Operations', () => {
|
||||
|
||||
await expect(page.getByRole('gridcell', { name: 'row one' })).toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'row two' })).toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('individual operations can be removed from the queue', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_remove_op`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName)
|
||||
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName)
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
await page.reload()
|
||||
@@ -256,16 +244,20 @@ test.describe('Queue Table Operations', () => {
|
||||
|
||||
await expect(page.getByRole('gridcell', { name: 'keep this row' })).toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'remove this row' })).not.toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('keyboard shortcuts work for queue operations', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_shortcuts`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName)
|
||||
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName)
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
await page.reload()
|
||||
@@ -291,15 +283,20 @@ test.describe('Queue Table Operations', () => {
|
||||
await expect(page.getByText('Changes saved successfully')).toBeVisible()
|
||||
|
||||
await expect(page.getByRole('gridcell', { name: 'shortcut test' })).toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('row deletes via context menu are queued', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_row_delete`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName, [{ name: 'row to delete' }])
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName, [{ name: 'row to delete' }])
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
@@ -330,15 +327,20 @@ test.describe('Queue Table Operations', () => {
|
||||
|
||||
await expect(page.getByRole('gridcell', { name: 'row to delete' })).not.toBeVisible()
|
||||
await expect(page.getByText('0 records')).toBeVisible()
|
||||
|
||||
await deleteTable(page, ref, tableName)
|
||||
})
|
||||
|
||||
test('row deletes can be cancelled', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_delete_cancel`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName, [{ name: 'should not be deleted' }])
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName, [{ name: 'should not be deleted' }])
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
@@ -359,18 +361,22 @@ test.describe('Queue Table Operations', () => {
|
||||
|
||||
await expect(page.getByRole('gridcell', { name: 'should not be deleted' })).toBeVisible()
|
||||
await expect(page.getByText('pending change')).not.toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('mixed operations (add, edit, delete) can be batched', async ({ page, ref }) => {
|
||||
const tableName = `${tableNamePrefix}_mixed_ops`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName, columnName, [
|
||||
{ name: 'row to edit' },
|
||||
{ name: 'row to delete' },
|
||||
])
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName, columnName, [
|
||||
{ name: 'row to edit' },
|
||||
{ name: 'row to delete' },
|
||||
])
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
@@ -411,8 +417,6 @@ test.describe('Queue Table Operations', () => {
|
||||
await expect(page.getByRole('gridcell', { name: 'new row' })).toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'row to delete' })).not.toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'row to edit' })).not.toBeVisible()
|
||||
|
||||
await dropTable(tableName)
|
||||
})
|
||||
|
||||
test('pending changes persist when switching between tables', async ({ page, ref }) => {
|
||||
@@ -420,8 +424,16 @@ test.describe('Queue Table Operations', () => {
|
||||
const tableName2 = `${tableNamePrefix}_persist2`
|
||||
const columnName = 'name'
|
||||
|
||||
await createTable(tableName1, columnName)
|
||||
await createTable(tableName2, columnName)
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTable(tableName1, columnName)
|
||||
await createTable(tableName2, columnName)
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName1)
|
||||
await dropTable(tableName2)
|
||||
}
|
||||
)
|
||||
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await enableQueueOperations(page)
|
||||
@@ -459,8 +471,5 @@ test.describe('Queue Table Operations', () => {
|
||||
|
||||
await page.getByRole('button', { name: `View ${tableName2}`, exact: true }).click()
|
||||
await expect(page.getByRole('gridcell', { name: 'pending in table 2' })).toBeVisible()
|
||||
|
||||
await dropTable(tableName1)
|
||||
await dropTable(tableName2)
|
||||
})
|
||||
})
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
} from '../utils/realtime-helpers.js'
|
||||
import { test } from '../utils/test.js'
|
||||
|
||||
const testChannelName = 'pw_realtime_test_channel'
|
||||
|
||||
test.describe('Realtime Inspector', () => {
|
||||
test.beforeEach(async ({ page, ref }) => {
|
||||
await navigateToRealtimeInspector(page, ref)
|
||||
@@ -41,6 +39,7 @@ test.describe('Realtime Inspector', () => {
|
||||
})
|
||||
|
||||
test('can join and leave a channel', async ({ page }) => {
|
||||
const testChannelName = 'pw_realtime_test_channel_join_leave'
|
||||
await joinChannel(page, testChannelName)
|
||||
|
||||
await expect(page.getByText('Listening', { exact: true })).toBeVisible({ timeout: 10000 })
|
||||
@@ -52,6 +51,7 @@ test.describe('Realtime Inspector', () => {
|
||||
})
|
||||
|
||||
test('start/stop listening button works', async ({ page }) => {
|
||||
const testChannelName = 'pw_realtime_test_channel_listening'
|
||||
await joinChannel(page, testChannelName)
|
||||
|
||||
await expect(page.getByText('Listening', { exact: true })).toBeVisible({ timeout: 10000 })
|
||||
@@ -72,6 +72,7 @@ test.describe('Realtime Inspector', () => {
|
||||
|
||||
test.describe('Broadcast Messages', () => {
|
||||
test('broadcast messages appear in the UI when listening', async ({ page }) => {
|
||||
const testChannelName = 'pw_realtime_test_channel_broadcast_ui'
|
||||
await joinChannel(page, testChannelName)
|
||||
|
||||
await expect(page.getByText('Listening', { exact: true })).toBeVisible({ timeout: 10000 })
|
||||
@@ -93,25 +94,26 @@ test.describe('Realtime Inspector', () => {
|
||||
})
|
||||
|
||||
test('clicking broadcast message shows detail panel', async ({ page }) => {
|
||||
const testChannelName = 'pw_realtime_test_channel_broadcast_details'
|
||||
await joinChannel(page, testChannelName)
|
||||
|
||||
await openBroadcastModal(page)
|
||||
await page.getByRole('button', { name: 'Confirm' }).click()
|
||||
await expect(page.getByText('Successfully broadcasted message')).toBeVisible({
|
||||
timeout: 10000,
|
||||
})
|
||||
await expect(page.getByText('Successfully broadcasted message')).toBeVisible()
|
||||
await expect(page.getByText('Broadcast a message to all clients')).not.toBeVisible()
|
||||
await waitForRealtimeMessage(page, { timeout: 30000 })
|
||||
|
||||
const messageRow = page.getByRole('row').filter({ hasText: 'broadcast' }).first()
|
||||
await expect(messageRow).toBeVisible({ timeout: 5000 })
|
||||
await expect(messageRow).toBeVisible()
|
||||
await messageRow.click()
|
||||
|
||||
await expect(page.getByText('Timestamp')).toBeVisible({ timeout: 5000 })
|
||||
await expect(page.getByText('Timestamp')).toBeVisible()
|
||||
|
||||
await leaveChannel(page)
|
||||
})
|
||||
|
||||
test('broadcast modal validates JSON payload', async ({ page }) => {
|
||||
const testChannelName = 'pw_realtime_test_channel_broadcast_json'
|
||||
await joinChannel(page, testChannelName)
|
||||
|
||||
await openBroadcastModal(page)
|
||||
@@ -134,7 +136,7 @@ test.describe('Realtime Inspector', () => {
|
||||
|
||||
test.describe('Message Display', () => {
|
||||
test('messages counter shows correct count', async ({ page }) => {
|
||||
await joinChannel(page, `${testChannelName}_counter`)
|
||||
await joinChannel(page, `pw_realtime_test_channel_counter`)
|
||||
|
||||
const initialCount = await getMessageCount(page)
|
||||
|
||||
|
||||
@@ -1,65 +1,9 @@
|
||||
import { expect, Page } from '@playwright/test'
|
||||
import { test } from '../utils/test.js'
|
||||
|
||||
import { test, withSetupCleanup } from '../utils/test.js'
|
||||
import { toUrl } from '../utils/to-url.js'
|
||||
import { createApiResponseWaiter, waitForApiResponse } from '../utils/wait-for-response.js'
|
||||
|
||||
const policyTableName = 'pw_rls_policy_test_table'
|
||||
const policySelectName = 'pw_test_select_policy'
|
||||
const policyInsertName = 'pw_test_insert_policy'
|
||||
const policyUpdateName = 'pw_test_update_policy'
|
||||
const policyDeleteName = 'pw_test_delete_policy'
|
||||
|
||||
/**
|
||||
* Helper function to create a test table for RLS policies
|
||||
*/
|
||||
const createTestTable = async (page: Page, ref: string) => {
|
||||
await page.goto(toUrl(`/project/${ref}/editor`))
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Check if table already exists
|
||||
const tableExists =
|
||||
(await page.getByRole('button', { name: `View ${policyTableName}` }).count()) > 0
|
||||
|
||||
if (!tableExists) {
|
||||
await page.getByRole('button', { name: 'New table', exact: true }).click()
|
||||
await page.getByTestId('table-name-input').fill(policyTableName)
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
|
||||
await expect(
|
||||
page.getByText(`Table ${policyTableName} is good to go!`),
|
||||
'Table creation confirmation should be visible'
|
||||
).toBeVisible({ timeout: 50000 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to delete the test table
|
||||
*/
|
||||
const deleteTestTable = async (page: Page, ref: string) => {
|
||||
await page.goto(toUrl(`/project/${ref}/editor`))
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
const tableExists =
|
||||
(await page.getByRole('button', { name: `View ${policyTableName}` }).count()) > 0
|
||||
|
||||
if (tableExists) {
|
||||
await page.getByLabel(`View ${policyTableName}`).nth(0).click()
|
||||
// Open the row actions menu (three dots) using the same selector pattern as table-editor.spec.ts.
|
||||
// This avoids brittle index-based selection and ignores the Unrestricted badge button.
|
||||
await page
|
||||
.getByLabel(`View ${policyTableName}`)
|
||||
.locator('button[aria-haspopup="menu"]')
|
||||
.click({ force: true })
|
||||
await page.getByText('Delete table').click()
|
||||
await page.getByRole('checkbox', { name: 'Drop table with cascade?' }).click()
|
||||
await page.getByRole('button', { name: 'Delete' }).click()
|
||||
|
||||
await expect(
|
||||
page.getByText(`Successfully deleted table "${policyTableName}"`),
|
||||
'Table deletion confirmation should be visible'
|
||||
).toBeVisible({ timeout: 50000 })
|
||||
}
|
||||
}
|
||||
import { createTableWithRLS, dropTable } from '../utils/db/queries.js'
|
||||
|
||||
/**
|
||||
* Helper function to navigate to policies page and wait for it to load
|
||||
@@ -104,27 +48,18 @@ const deletePolicyIfExists = async (page: Page, ref: string, policyNameToDelete:
|
||||
}
|
||||
}
|
||||
|
||||
test.describe.serial('RLS Policies', () => {
|
||||
let page: Page
|
||||
|
||||
test.beforeAll(async ({ browser, ref }) => {
|
||||
page = await browser.newPage()
|
||||
|
||||
// Create test table
|
||||
await createTestTable(page, ref)
|
||||
|
||||
// Navigate to policies page
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
})
|
||||
|
||||
test.afterAll(async ({ ref }) => {
|
||||
// Clean up: delete test table
|
||||
await deleteTestTable(page, ref)
|
||||
await page.close()
|
||||
})
|
||||
|
||||
test.describe('RLS Policies', () => {
|
||||
test.describe('Policies Page', () => {
|
||||
test('should display policies page correctly', async ({ ref }) => {
|
||||
test('should display policies page correctly', async ({ page, ref }) => {
|
||||
const policyTableName = 'pw_rls_policy_test_table'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
// Check page elements
|
||||
@@ -143,7 +78,16 @@ test.describe.serial('RLS Policies', () => {
|
||||
await expect(page.getByText(policyTableName)).toBeVisible()
|
||||
})
|
||||
|
||||
test('should filter tables and policies by search', async ({ ref }) => {
|
||||
test('should filter tables and policies by search', async ({ page, ref }) => {
|
||||
const policyTableName = 'pw_rls_policy_filter_table'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
const searchInput = page.getByPlaceholder('Filter tables and policies')
|
||||
@@ -160,7 +104,7 @@ test.describe.serial('RLS Policies', () => {
|
||||
await page.waitForTimeout(500)
|
||||
})
|
||||
|
||||
test('should switch between schemas', async ({ ref }) => {
|
||||
test('should switch between schemas', async ({ page, ref }) => {
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
// Click schema selector
|
||||
@@ -181,7 +125,19 @@ test.describe.serial('RLS Policies', () => {
|
||||
})
|
||||
|
||||
test.describe('Table editor RLS badge', () => {
|
||||
test('shows Unrestricted badge when RLS is disabled for public table', async ({ ref }) => {
|
||||
test('shows Unrestricted badge when RLS is disabled for public table', async ({
|
||||
page,
|
||||
ref,
|
||||
}) => {
|
||||
const policyTableName = 'pw_rls_policy_unrestricted_table'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
// First, ensure RLS is disabled for the test table via the Policies page
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
@@ -241,12 +197,19 @@ test.describe.serial('RLS Policies', () => {
|
||||
})
|
||||
|
||||
test.describe('Create RLS Policy', () => {
|
||||
test('should create a SELECT policy successfully', async ({ ref }) => {
|
||||
test('should create a SELECT policy successfully', async ({ page, ref }) => {
|
||||
const policyTableName = 'pw_rls_policy_select_table'
|
||||
const policySelectName = 'pw_test_select_policy'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
// Delete policy if it exists from previous run
|
||||
await deletePolicyIfExists(page, ref, policySelectName)
|
||||
|
||||
// Find the test table and click "Create policy"
|
||||
await page.getByTestId(`${policyTableName}-create-policy`).click()
|
||||
|
||||
@@ -256,6 +219,9 @@ test.describe.serial('RLS Policies', () => {
|
||||
'Policy creation dialog should open'
|
||||
).toBeVisible()
|
||||
|
||||
// Hide the sidebar tools
|
||||
await page.getByText('Hide tools').click()
|
||||
|
||||
// Fill in policy name
|
||||
await page.getByRole('textbox', { name: 'Policy Name' }).fill(policySelectName)
|
||||
|
||||
@@ -287,12 +253,19 @@ test.describe.serial('RLS Policies', () => {
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^public$/ })).toBeVisible()
|
||||
})
|
||||
|
||||
test('should create an INSERT policy with authenticated role', async ({ ref }) => {
|
||||
test('should create an INSERT policy with authenticated role', async ({ page, ref }) => {
|
||||
const policyTableName = 'pw_rls_policy_insert_table'
|
||||
const policyInsertName = 'pw_test_insert_policy'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
// Delete policy if it exists
|
||||
await deletePolicyIfExists(page, ref, policyInsertName)
|
||||
|
||||
// Open create policy dialog
|
||||
await page.getByTestId(`${policyTableName}-create-policy`).click()
|
||||
|
||||
@@ -300,6 +273,9 @@ test.describe.serial('RLS Policies', () => {
|
||||
page.getByRole('heading', { name: 'Create a new Row Level Security policy' })
|
||||
).toBeVisible()
|
||||
|
||||
// Hide the sidebar tools
|
||||
await page.getByText('Hide tools').click()
|
||||
|
||||
// Fill in policy name
|
||||
await page.getByRole('textbox', { name: 'Policy Name' }).fill(policyInsertName)
|
||||
|
||||
@@ -332,12 +308,19 @@ test.describe.serial('RLS Policies', () => {
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^authenticated$/ })).toBeVisible()
|
||||
})
|
||||
|
||||
test('should create an UPDATE policy with custom condition', async ({ ref }) => {
|
||||
test('should create an UPDATE policy with custom condition', async ({ page, ref }) => {
|
||||
const policyTableName = 'pw_rls_policy_update_table'
|
||||
const policyUpdateName = 'pw_test_update_policy'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
// Delete policy if it exists
|
||||
await deletePolicyIfExists(page, ref, policyUpdateName)
|
||||
|
||||
// Open create policy dialog
|
||||
await page.getByTestId(`${policyTableName}-create-policy`).click()
|
||||
|
||||
@@ -345,6 +328,9 @@ test.describe.serial('RLS Policies', () => {
|
||||
page.getByRole('heading', { name: 'Create a new Row Level Security policy' })
|
||||
).toBeVisible()
|
||||
|
||||
// Hide the sidebar tools
|
||||
await page.getByText('Hide tools').click()
|
||||
|
||||
// Fill in policy name
|
||||
await page.getByRole('textbox', { name: 'Policy Name' }).fill(policyUpdateName)
|
||||
|
||||
@@ -374,12 +360,19 @@ test.describe.serial('RLS Policies', () => {
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^UPDATE$/ })).toBeVisible()
|
||||
})
|
||||
|
||||
test('should create a DELETE policy', async ({ ref }) => {
|
||||
test('should create a DELETE policy', async ({ page, ref }) => {
|
||||
const policyTableName = 'pw_rls_policy_delete_table'
|
||||
const policyDeleteName = 'pw_test_delete_policy'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
// Delete policy if it exists
|
||||
await deletePolicyIfExists(page, ref, policyDeleteName)
|
||||
|
||||
// Open create policy dialog
|
||||
await page.getByTestId(`${policyTableName}-create-policy`).click()
|
||||
|
||||
@@ -387,6 +380,9 @@ test.describe.serial('RLS Policies', () => {
|
||||
page.getByRole('heading', { name: 'Create a new Row Level Security policy' })
|
||||
).toBeVisible()
|
||||
|
||||
// Hide the sidebar tools
|
||||
await page.getByText('Hide tools').click()
|
||||
|
||||
// Fill in policy name
|
||||
await page.getByRole('textbox', { name: 'Policy Name' }).fill(policyDeleteName)
|
||||
|
||||
@@ -416,7 +412,16 @@ test.describe.serial('RLS Policies', () => {
|
||||
await expect(policyRow.locator('code').filter({ hasText: /^DELETE$/ })).toBeVisible()
|
||||
})
|
||||
|
||||
test('should cancel policy creation', async ({ ref }) => {
|
||||
test('should cancel policy creation', async ({ page, ref }) => {
|
||||
const policyTableName = 'pw_rls_policy_cancel_table'
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
await createTableWithRLS(policyTableName, 'test')
|
||||
},
|
||||
async () => {
|
||||
await dropTable(policyTableName)
|
||||
}
|
||||
)
|
||||
await navigateToPoliciesPage(page, ref)
|
||||
|
||||
// Open create policy dialog
|
||||
@@ -430,7 +435,7 @@ test.describe.serial('RLS Policies', () => {
|
||||
await page.getByRole('textbox', { name: 'Policy Name' }).fill('policy_to_cancel')
|
||||
|
||||
// Click cancel
|
||||
await page.getByRole('button', { name: 'Cancel' }).click()
|
||||
await page.getByRole('button', { name: 'Cancel', exact: true }).click()
|
||||
|
||||
// Dialog should close
|
||||
await expect(
|
||||
|
||||
Reference in new issue
Block a user