chore: add e2e test for sql editor snippets (#36293)

* chore: add e2e test for sql editor snippets

* trigger build

* chore: fix failing test and add rls

---------

Co-authored-by: Terry Sutton <saltcod@gmail.com>
This commit is contained in:
Michael Ong
2025-07-01 20:16:54 +08:00
committed by GitHub
parent 8c867d874d
commit a259c0b9dc
6 changed files with 177 additions and 62 deletions

View File

@@ -80,6 +80,7 @@ const UtilityActions = ({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
data-testId="sql-editor-utility-actions"
type="default"
className={cn('px-1', isAiOpen ? 'block 2xl:hidden' : 'hidden')}
icon={<MoreVertical className="text-foreground-light" />}

View File

@@ -134,6 +134,7 @@ export const SQLEditorMenu = () => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
data-testId="sql-editor-new-query-button"
type="default"
icon={<Plus className="text-foreground" />}
className="w-[26px]"

View File

@@ -4,9 +4,8 @@ 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()
await expect(page.getByRole('link', { name: 'Tables' })).toBeVisible()
})
})

View File

@@ -34,3 +34,68 @@ test.describe('SQL Editor', () => {
await expect(result).toBeVisible()
})
})
test.describe('SQL Snippets', () => {
test('should create and load a new snippet', async ({ page }) => {
await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql`))
const addButton = page.getByTestId('sql-editor-new-query-button')
const runButton = page.getByTestId('sql-run-button')
await page.getByRole('button', { name: 'Favorites' }).click()
await page.getByRole('button', { name: 'Shared' }).click()
await expect(page.getByText('No shared queries')).toBeVisible()
await expect(page.getByText('No favorite queries')).toBeVisible()
// write some sql in the editor
await addButton.click()
await page.getByRole('menuitem', { name: 'Create a new snippet' }).click()
const editor = page.getByRole('code').nth(0)
await page.waitForTimeout(1000)
await editor.click()
await page.keyboard.type(`select 'hello world';`)
await expect(page.getByText("select 'hello world';")).toBeVisible()
await runButton.click()
// snippet exists
const privateSnippet = page.getByLabel('private-snippets')
await expect(privateSnippet).toContainText('Untitled query')
// favourite snippets
await page.getByTestId('sql-editor-utility-actions').click()
await page.getByRole('menuitem', { name: 'Add to favorites', exact: true }).click()
const favouriteSnippetsSection = page.getByLabel('favorite-snippets')
await expect(favouriteSnippetsSection).toContainText('Untitled query')
// unfavorite snippets
await page.waitForTimeout(500)
await page.getByTestId('sql-editor-utility-actions').click()
await page.getByRole('menuitem', { name: 'Remove from favorites' }).click()
await expect(favouriteSnippetsSection).not.toBeVisible()
// rename snippet
await privateSnippet.getByText('Untitled query').click({ button: 'right' })
await page.getByRole('menuitem', { name: 'Rename query', exact: true }).click()
await expect(page.getByRole('heading', { name: 'Rename' })).toBeVisible()
await page.getByRole('textbox', { name: 'Name' }).fill('test snippet')
await page.getByRole('button', { name: 'Rename query', exact: true }).click()
const privateSnippet2 = privateSnippet.getByText('test snippet', { exact: true })
await expect(privateSnippet2).toBeVisible()
// share with a team
await privateSnippet2.click({ button: 'right' })
await page.getByRole('menuitem', { name: 'Share query with team' }).click()
await expect(page.getByRole('heading', { name: 'Confirm to share query: test' })).toBeVisible()
await page.getByRole('button', { name: 'Share query', exact: true }).click()
const sharedSnippet = await page.getByLabel('project-level-snippets')
await expect(sharedSnippet).toContainText('test snippet')
// unshare a snippet
await sharedSnippet.getByText('test snippet').click({ button: 'right' })
await page.getByRole('menuitem', { name: 'Unshare query with team' }).click()
await expect(page.getByRole('heading', { name: 'Confirm to unshare query:' })).toBeVisible()
await page.getByRole('button', { name: 'Unshare query', exact: true }).click()
await expect(sharedSnippet).not.toBeVisible()
})
})

View File

@@ -2,9 +2,6 @@ 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 }),
@@ -41,12 +38,67 @@ const getSelectors = (tableName: string) => ({
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 "'),
deleteTableToast: (page, tableName) =>
page.getByText(`Successfully deleted table "${tableName}"`),
})
const createTable = async (page: Page, tableName: string) => {
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()
}
const deleteTables = async (page: Page, tableName: string) => {
const s = getSelectors(tableName)
await page.waitForTimeout(500)
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, tableName),
'Delete confirmation toast should be visible'
).toBeVisible()
}
test.describe('Table Editor', () => {
let page: Page
let tableName: string
const testTableName = `pw-test-table-editor`
const tableNameRlsEnabled = `pw-test-rls-enabled`
const tableNameRlsDisabled = `pw-test-rls-disabled`
test.beforeAll(async ({ browser, ref }) => {
test.setTimeout(60000)
@@ -55,66 +107,27 @@ test.describe('Table Editor', () => {
* 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()
await page.waitForTimeout(2000)
// delete table name if it exists
await deleteTables(page, testTableName)
await deleteTables(page, tableNameRlsEnabled)
await deleteTables(page, tableNameRlsDisabled)
})
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()
// delete all tables related to this test
await deleteTables(page, testTableName)
await deleteTables(page, tableNameRlsEnabled)
await deleteTables(page, tableNameRlsDisabled)
})
test('should perform all table operations sequentially', async ({ ref }) => {
const s = getSelectors(tableName)
await createTable(page, testTableName)
const s = getSelectors(testTableName)
test.setTimeout(60000)
// 1. View table definition
@@ -124,17 +137,18 @@ test.describe('Table Editor', () => {
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;`
`create table public.pw - test - table - editor ( 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 pw - test - table - editor_pkey primary key (id)) TABLESPACE pg_default;
`
)
// 2. Insert test data
await page.getByRole('button', { name: `View ${tableName}` }).click()
await page.getByRole('button', { name: `View ${testTableName}` }).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 page.getByRole('button', { name: `View ${testTableName}` }).click()
await s.insertRowBtn(page).click()
await s.insertModal(page).click()
await s.defaultValueInput(page).fill('4')
@@ -151,7 +165,11 @@ test.describe('Table Editor', () => {
await page.keyboard.down('Escape')
// Wait for sorting to complete
await page.waitForResponse((response) => response.url().includes(`pg-meta/${ref}/query`))
await page.waitForResponse(
(response) =>
response.url().includes(`pg-meta/${ref}/query`) ||
response.url().includes('pg-meta/default/query')
)
// give it a second to rerender
await page.waitForTimeout(1000)
@@ -206,5 +224,36 @@ test.describe('Table Editor', () => {
page.getByTestId('tables-list'),
'Tables list should be visible in public schema'
).toBeVisible()
await deleteTables(page, testTableName)
})
test('should show rls accordingly', async () => {
await createTable(page, tableNameRlsEnabled)
// testing rls enabled
await page.getByRole('button', { name: `View ${tableNameRlsEnabled}` }).click()
await expect(page.getByRole('link', { name: 'Add RLS policy' })).toBeVisible()
// testing rls disabled
const s2 = getSelectors(tableNameRlsDisabled)
await s2.newTableBtn(page).click()
await s2.tableNameInput(page).fill(tableNameRlsDisabled)
await s2.rlsCheckbox(page).click()
await s2.rlsConfirmBtn(page).click()
await s2.saveBtn(page).click()
await expect(
page.getByText(`Table ${tableNameRlsDisabled} is good to go!`),
'Success toast should be visible after Rls disabled table is created.'
).toBeVisible({
timeout: 50000,
})
await page.getByRole('button', { name: `View ${tableNameRlsDisabled}` }).click()
await expect(page.getByRole('button', { name: 'RLS disabled' })).toBeVisible()
await deleteTables(page, tableNameRlsEnabled)
await deleteTables(page, tableNameRlsDisabled)
})
})

View File

@@ -12,7 +12,7 @@ export default defineConfig({
testDir: './features',
testMatch: /.*\.spec\.ts/,
forbidOnly: IS_CI,
retries: 3,
retries: IS_CI ? 3 : 1,
use: {
baseURL: env.STUDIO_URL,
screenshot: 'off',