Skip to content

Commit a259c0b

Browse files
Michaelomhsaltcod
andauthored
chore: add e2e test for sql editor snippets (supabase#36293)
* chore: add e2e test for sql editor snippets * trigger build * chore: fix failing test and add rls --------- Co-authored-by: Terry Sutton <[email protected]>
1 parent 8c867d8 commit a259c0b

File tree

6 files changed

+177
-62
lines changed

6 files changed

+177
-62
lines changed

apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const UtilityActions = ({
8080
<DropdownMenu>
8181
<DropdownMenuTrigger asChild>
8282
<Button
83+
data-testId="sql-editor-utility-actions"
8384
type="default"
8485
className={cn('px-1', isAiOpen ? 'block 2xl:hidden' : 'hidden')}
8586
icon={<MoreVertical className="text-foreground-light" />}

apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export const SQLEditorMenu = () => {
134134
<DropdownMenu>
135135
<DropdownMenuTrigger asChild>
136136
<Button
137+
data-testId="sql-editor-new-query-button"
137138
type="default"
138139
icon={<Plus className="text-foreground" />}
139140
className="w-[26px]"

e2e/studio/features/home.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { toUrl } from '../utils/to-url'
44

55
test.describe('Project', async () => {
66
test('Can navigate to project home page', async ({ page, ref }) => {
7-
console.log(page.url())
87
await page.goto(toUrl(`/project/${ref}`))
98

10-
await expect(page.getByRole('button', { name: 'Project Status' })).toBeVisible()
9+
await expect(page.getByRole('link', { name: 'Tables' })).toBeVisible()
1110
})
1211
})

e2e/studio/features/sql-editor.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,68 @@ test.describe('SQL Editor', () => {
3434
await expect(result).toBeVisible()
3535
})
3636
})
37+
38+
test.describe('SQL Snippets', () => {
39+
test('should create and load a new snippet', async ({ page }) => {
40+
await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql`))
41+
42+
const addButton = page.getByTestId('sql-editor-new-query-button')
43+
const runButton = page.getByTestId('sql-run-button')
44+
await page.getByRole('button', { name: 'Favorites' }).click()
45+
await page.getByRole('button', { name: 'Shared' }).click()
46+
await expect(page.getByText('No shared queries')).toBeVisible()
47+
await expect(page.getByText('No favorite queries')).toBeVisible()
48+
49+
// write some sql in the editor
50+
await addButton.click()
51+
await page.getByRole('menuitem', { name: 'Create a new snippet' }).click()
52+
53+
const editor = page.getByRole('code').nth(0)
54+
await page.waitForTimeout(1000)
55+
await editor.click()
56+
await page.keyboard.type(`select 'hello world';`)
57+
await expect(page.getByText("select 'hello world';")).toBeVisible()
58+
await runButton.click()
59+
60+
// snippet exists
61+
const privateSnippet = page.getByLabel('private-snippets')
62+
await expect(privateSnippet).toContainText('Untitled query')
63+
64+
// favourite snippets
65+
await page.getByTestId('sql-editor-utility-actions').click()
66+
await page.getByRole('menuitem', { name: 'Add to favorites', exact: true }).click()
67+
const favouriteSnippetsSection = page.getByLabel('favorite-snippets')
68+
await expect(favouriteSnippetsSection).toContainText('Untitled query')
69+
70+
// unfavorite snippets
71+
await page.waitForTimeout(500)
72+
await page.getByTestId('sql-editor-utility-actions').click()
73+
await page.getByRole('menuitem', { name: 'Remove from favorites' }).click()
74+
await expect(favouriteSnippetsSection).not.toBeVisible()
75+
76+
// rename snippet
77+
await privateSnippet.getByText('Untitled query').click({ button: 'right' })
78+
await page.getByRole('menuitem', { name: 'Rename query', exact: true }).click()
79+
await expect(page.getByRole('heading', { name: 'Rename' })).toBeVisible()
80+
await page.getByRole('textbox', { name: 'Name' }).fill('test snippet')
81+
await page.getByRole('button', { name: 'Rename query', exact: true }).click()
82+
83+
const privateSnippet2 = privateSnippet.getByText('test snippet', { exact: true })
84+
await expect(privateSnippet2).toBeVisible()
85+
86+
// share with a team
87+
await privateSnippet2.click({ button: 'right' })
88+
await page.getByRole('menuitem', { name: 'Share query with team' }).click()
89+
await expect(page.getByRole('heading', { name: 'Confirm to share query: test' })).toBeVisible()
90+
await page.getByRole('button', { name: 'Share query', exact: true }).click()
91+
const sharedSnippet = await page.getByLabel('project-level-snippets')
92+
await expect(sharedSnippet).toContainText('test snippet')
93+
94+
// unshare a snippet
95+
await sharedSnippet.getByText('test snippet').click({ button: 'right' })
96+
await page.getByRole('menuitem', { name: 'Unshare query with team' }).click()
97+
await expect(page.getByRole('heading', { name: 'Confirm to unshare query:' })).toBeVisible()
98+
await page.getByRole('button', { name: 'Unshare query', exact: true }).click()
99+
await expect(sharedSnippet).not.toBeVisible()
100+
})
101+
})

e2e/studio/features/table-editor.spec.ts

Lines changed: 108 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import { expect, Page } from '@playwright/test'
22
import { test } from '../utils/test'
33
import { toUrl } from '../utils/to-url'
44

5-
// Helper to generate a random table name
6-
const getRandomTableName = () => `pw-test-${Math.floor(Math.random() * 10000)}`
7-
85
const getSelectors = (tableName: string) => ({
96
tableButton: (page) => page.getByRole('button', { name: `View ${tableName}` }),
107
newTableBtn: (page) => page.getByRole('button', { name: 'New table', exact: true }),
@@ -41,12 +38,67 @@ const getSelectors = (tableName: string) => ({
4138
confirmDeleteBtn: (page) => page.getByRole('button', { name: 'Delete' }),
4239
rlsCheckbox: (page) => page.getByLabel('Enable Row Level Security ('),
4340
rlsConfirmBtn: (page) => page.getByRole('button', { name: 'Confirm' }),
44-
deleteTableToast: (page) => page.getByText('Successfully deleted table "'),
41+
deleteTableToast: (page, tableName) =>
42+
page.getByText(`Successfully deleted table "${tableName}"`),
4543
})
4644

45+
const createTable = async (page: Page, tableName: string) => {
46+
const s = getSelectors(tableName)
47+
48+
await s.newTableBtn(page).click()
49+
await s.tableNameInput(page).fill(tableName)
50+
51+
await s.createdAtExtraOptions(page).click()
52+
await page.getByText('Is Nullable').click()
53+
await s.createdAtExtraOptions(page).click({ force: true })
54+
55+
await s.addColumnBtn(page).click()
56+
await s.columnNameInput(page).fill('defaultValueColumn')
57+
await s.chooseColumnType(page).click()
58+
await s.signedIntOption(page).click()
59+
await s.defaultValueField(page).click()
60+
await s.defaultValueField(page).fill('2')
61+
62+
await s.saveBtn(page).click()
63+
64+
// wait till we see the success toast
65+
// Text: Table tableName is good to go!
66+
67+
await expect(
68+
page.getByText(`Table ${tableName} is good to go!`),
69+
'Success toast should be visible after table creation'
70+
).toBeVisible({
71+
timeout: 50000,
72+
})
73+
74+
await expect(
75+
page.getByRole('button', { name: `View ${tableName}` }),
76+
'Table should be visible after creation'
77+
).toBeVisible()
78+
}
79+
80+
const deleteTables = async (page: Page, tableName: string) => {
81+
const s = getSelectors(tableName)
82+
83+
await page.waitForTimeout(500)
84+
const exists = (await s.tableButton(page).count()) > 0
85+
if (!exists) return
86+
87+
await s.viewTableLabel(page).click()
88+
await s.viewTableLabel(page).getByRole('button').nth(1).click()
89+
await s.deleteTableBtn(page).click()
90+
await s.confirmDeleteBtn(page).click()
91+
await expect(
92+
s.deleteTableToast(page, tableName),
93+
'Delete confirmation toast should be visible'
94+
).toBeVisible()
95+
}
96+
4797
test.describe('Table Editor', () => {
4898
let page: Page
49-
let tableName: string
99+
const testTableName = `pw-test-table-editor`
100+
const tableNameRlsEnabled = `pw-test-rls-enabled`
101+
const tableNameRlsDisabled = `pw-test-rls-disabled`
50102

51103
test.beforeAll(async ({ browser, ref }) => {
52104
test.setTimeout(60000)
@@ -55,66 +107,27 @@ test.describe('Table Editor', () => {
55107
* Create a new table for the tests
56108
*/
57109
page = await browser.newPage()
58-
59110
await page.goto(toUrl(`/project/${ref}/editor`))
60111

61-
tableName = getRandomTableName()
62-
const s = getSelectors(tableName)
63-
64-
await s.newTableBtn(page).click()
65-
await s.tableNameInput(page).fill(tableName)
66-
67-
await s.createdAtExtraOptions(page).click()
68-
await page.getByText('Is Nullable').click()
69-
await s.createdAtExtraOptions(page).click({ force: true })
70-
71-
await s.addColumnBtn(page).click()
72-
await s.columnNameInput(page).fill('defaultValueColumn')
73-
await s.chooseColumnType(page).click()
74-
await s.signedIntOption(page).click()
75-
await s.defaultValueField(page).click()
76-
await s.defaultValueField(page).fill('2')
77-
78-
await s.saveBtn(page).click()
79-
80-
// wait till we see the success toast
81-
// Text: Table tableName is good to go!
82-
83-
await expect(
84-
page.getByText(`Table ${tableName} is good to go!`),
85-
'Success toast should be visible after table creation'
86-
).toBeVisible({
87-
timeout: 50000,
88-
})
89-
90-
await expect(
91-
page.getByRole('button', { name: `View ${tableName}` }),
92-
'Table should be visible after creation'
93-
).toBeVisible()
112+
await page.waitForTimeout(2000)
113+
// delete table name if it exists
114+
await deleteTables(page, testTableName)
115+
await deleteTables(page, tableNameRlsEnabled)
116+
await deleteTables(page, tableNameRlsDisabled)
94117
})
95118

96119
test.afterAll(async () => {
97120
test.setTimeout(60000)
98-
/**
99-
* Delete the table after the tests are done
100-
*/
101-
const s = getSelectors(tableName)
102-
103-
const exists = (await s.tableButton(page).count()) > 0
104-
if (!exists) return
105121

106-
await s.viewTableLabel(page).click()
107-
await s.viewTableLabel(page).getByRole('button').nth(1).click()
108-
await s.deleteTableBtn(page).click()
109-
await s.confirmDeleteBtn(page).click()
110-
await expect(
111-
s.deleteTableToast(page),
112-
'Delete confirmation toast should be visible'
113-
).toBeVisible()
122+
// delete all tables related to this test
123+
await deleteTables(page, testTableName)
124+
await deleteTables(page, tableNameRlsEnabled)
125+
await deleteTables(page, tableNameRlsDisabled)
114126
})
115127

116128
test('should perform all table operations sequentially', async ({ ref }) => {
117-
const s = getSelectors(tableName)
129+
await createTable(page, testTableName)
130+
const s = getSelectors(testTableName)
118131
test.setTimeout(60000)
119132

120133
// 1. View table definition
@@ -124,17 +137,18 @@ test.describe('Table Editor', () => {
124137
s.viewLines(page),
125138
'Table definition should contain the correct SQL'
126139
).toContainText(
127-
`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;`
140+
`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;
141+
`
128142
)
129143

130144
// 2. Insert test data
131-
await page.getByRole('button', { name: `View ${tableName}` }).click()
145+
await page.getByRole('button', { name: `View ${testTableName}` }).click()
132146
await s.insertRowBtn(page).click()
133147
await s.insertModal(page).click()
134148
await s.defaultValueInput(page).fill('100')
135149
await s.actionBarSaveRow(page).click()
136150

137-
await page.getByRole('button', { name: `View ${tableName}` }).click()
151+
await page.getByRole('button', { name: `View ${testTableName}` }).click()
138152
await s.insertRowBtn(page).click()
139153
await s.insertModal(page).click()
140154
await s.defaultValueInput(page).fill('4')
@@ -151,7 +165,11 @@ test.describe('Table Editor', () => {
151165
await page.keyboard.down('Escape')
152166

153167
// Wait for sorting to complete
154-
await page.waitForResponse((response) => response.url().includes(`pg-meta/${ref}/query`))
168+
await page.waitForResponse(
169+
(response) =>
170+
response.url().includes(`pg-meta/${ref}/query`) ||
171+
response.url().includes('pg-meta/default/query')
172+
)
155173

156174
// give it a second to rerender
157175
await page.waitForTimeout(1000)
@@ -206,5 +224,36 @@ test.describe('Table Editor', () => {
206224
page.getByTestId('tables-list'),
207225
'Tables list should be visible in public schema'
208226
).toBeVisible()
227+
228+
await deleteTables(page, testTableName)
229+
})
230+
231+
test('should show rls accordingly', async () => {
232+
await createTable(page, tableNameRlsEnabled)
233+
234+
// testing rls enabled
235+
await page.getByRole('button', { name: `View ${tableNameRlsEnabled}` }).click()
236+
await expect(page.getByRole('link', { name: 'Add RLS policy' })).toBeVisible()
237+
238+
// testing rls disabled
239+
const s2 = getSelectors(tableNameRlsDisabled)
240+
await s2.newTableBtn(page).click()
241+
await s2.tableNameInput(page).fill(tableNameRlsDisabled)
242+
await s2.rlsCheckbox(page).click()
243+
await s2.rlsConfirmBtn(page).click()
244+
await s2.saveBtn(page).click()
245+
246+
await expect(
247+
page.getByText(`Table ${tableNameRlsDisabled} is good to go!`),
248+
'Success toast should be visible after Rls disabled table is created.'
249+
).toBeVisible({
250+
timeout: 50000,
251+
})
252+
253+
await page.getByRole('button', { name: `View ${tableNameRlsDisabled}` }).click()
254+
await expect(page.getByRole('button', { name: 'RLS disabled' })).toBeVisible()
255+
256+
await deleteTables(page, tableNameRlsEnabled)
257+
await deleteTables(page, tableNameRlsDisabled)
209258
})
210259
})

e2e/studio/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default defineConfig({
1212
testDir: './features',
1313
testMatch: /.*\.spec\.ts/,
1414
forbidOnly: IS_CI,
15-
retries: 3,
15+
retries: IS_CI ? 3 : 1,
1616
use: {
1717
baseURL: env.STUDIO_URL,
1818
screenshot: 'off',

0 commit comments

Comments
 (0)