|
1 | | -import { expect } from '@playwright/test' |
2 | | -import { env } from '../env.config' |
| 1 | +import { expect, Page } from '@playwright/test' |
| 2 | +import { isCLI } from '../utils/is-cli' |
3 | 3 | import { test } from '../utils/test' |
4 | 4 | import { toUrl } from '../utils/to-url' |
5 | 5 |
|
| 6 | +const deleteQuery = async (page: Page, queryName: string) => { |
| 7 | + const privateSnippet = page.getByLabel('private-snippets') |
| 8 | + await privateSnippet.getByText(queryName).first().click({ button: 'right' }) |
| 9 | + await page.getByRole('menuitem', { name: 'Delete query' }).click() |
| 10 | + await expect(page.getByRole('heading', { name: 'Confirm to delete query' })).toBeVisible() |
| 11 | + await page.getByRole('button', { name: 'Delete 1 query' }).click() |
| 12 | +} |
| 13 | + |
6 | 14 | test.describe('SQL Editor', () => { |
7 | | - test('should check if SQL editor can run simple commands', async ({ page }) => { |
8 | | - await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql/new?skip=true`)) |
| 15 | + let page: Page |
| 16 | + const pwTestQueryName = 'pw-test-query' |
9 | 17 |
|
10 | | - const editor = page.getByRole('code').nth(0) |
| 18 | + test.beforeAll(async ({ browser, ref }) => { |
| 19 | + test.setTimeout(60000) |
| 20 | + |
| 21 | + // Create a new table for the tests |
| 22 | + page = await browser.newPage() |
| 23 | + await page.goto(toUrl(`/project/${ref}/sql/new?skip=true`)) |
| 24 | + |
| 25 | + await page.evaluate((ref) => { |
| 26 | + localStorage.removeItem('dashboard-history-default') |
| 27 | + localStorage.removeItem(`dashboard-history-${ref}`) |
| 28 | + }, ref) |
| 29 | + |
| 30 | + // intercept AI title generation to prevent flaky tests |
| 31 | + await page.route('**/dashboard/api/ai/sql/title-v2', async (route) => { |
| 32 | + await route.abort() |
| 33 | + }) |
| 34 | + }) |
| 35 | + |
| 36 | + test.beforeEach(async ({ ref }) => { |
| 37 | + if ((await page.getByLabel('private-snippets').count()) === 0) { |
| 38 | + return |
| 39 | + } |
| 40 | + |
| 41 | + // since in local, we don't have access to the supabase platform, reloading would reload all the sql snippets. |
| 42 | + if (isCLI()) { |
| 43 | + await page.reload() |
| 44 | + } |
| 45 | + |
| 46 | + // remove sql snippets for - "Untitled query" and "pw test query" |
| 47 | + const privateSnippet = page.getByLabel('private-snippets') |
| 48 | + let privateSnippetText = await privateSnippet.textContent() |
| 49 | + while (privateSnippetText.includes('Untitled query')) { |
| 50 | + deleteQuery(page, 'Untitled query') |
| 51 | + |
| 52 | + await page.waitForResponse( |
| 53 | + (response) => |
| 54 | + (response.url().includes(`projects/${ref}/content`) || |
| 55 | + response.url().includes('projects/default/content')) && |
| 56 | + response.request().method() === 'DELETE' |
| 57 | + ) |
| 58 | + await expect( |
| 59 | + page.getByText('Successfully deleted 1 query'), |
| 60 | + 'Delete confirmation toast should be visible' |
| 61 | + ).toBeVisible({ |
| 62 | + timeout: 50000, |
| 63 | + }) |
| 64 | + await page.waitForTimeout(1000) |
| 65 | + privateSnippetText = |
| 66 | + (await page.getByLabel('private-snippets').count()) > 0 |
| 67 | + ? await privateSnippet.textContent() |
| 68 | + : '' |
| 69 | + } |
| 70 | + |
| 71 | + while (privateSnippetText.includes(pwTestQueryName)) { |
| 72 | + deleteQuery(page, pwTestQueryName) |
| 73 | + await page.waitForResponse( |
| 74 | + (response) => |
| 75 | + (response.url().includes(`projects/${ref}/content`) || |
| 76 | + response.url().includes('projects/default/content')) && |
| 77 | + response.request().method() === 'DELETE' |
| 78 | + ) |
| 79 | + await expect( |
| 80 | + page.getByText('Successfully deleted 1 query'), |
| 81 | + 'Delete confirmation toast should be visible' |
| 82 | + ).toBeVisible({ |
| 83 | + timeout: 50000, |
| 84 | + }) |
| 85 | + await page.waitForTimeout(1000) |
| 86 | + privateSnippetText = |
| 87 | + (await page.getByLabel('private-snippets').count()) > 0 |
| 88 | + ? await privateSnippet.textContent() |
| 89 | + : '' |
| 90 | + } |
| 91 | + }) |
| 92 | + |
| 93 | + test('should check if SQL editor can run simple commands', async () => { |
| 94 | + await page.getByTestId('sql-editor-new-query-button').click() |
| 95 | + await page.getByRole('menuitem', { name: 'Create a new snippet' }).click() |
11 | 96 |
|
12 | 97 | // write some sql in the editor |
13 | 98 | // This has to be done since the editor is not editable (input, textarea, etc.) |
14 | | - await editor.click() |
| 99 | + await page.waitForTimeout(1000) |
| 100 | + const editor = page.getByRole('code').nth(0) |
15 | 101 | await editor.click() |
16 | 102 | await page.keyboard.press('ControlOrMeta+KeyA') |
17 | 103 | await page.keyboard.type(`select 'hello world';`) |
| 104 | + await page.getByTestId('sql-run-button').click() |
18 | 105 |
|
19 | | - await page.getByRole('button', { name: /^Run( CTRL)?$/, exact: false }).click() |
| 106 | + // verify the result |
| 107 | + await expect(page.getByRole('gridcell', { name: 'hello world' })).toBeVisible({ |
| 108 | + timeout: 5000, |
| 109 | + }) |
20 | 110 |
|
21 | | - // Should say "Running..." |
22 | | - await expect(page.getByText('Running...')).toBeVisible() |
| 111 | + // SQL written in the editor should not be the previous query. |
| 112 | + await page.waitForTimeout(1000) |
| 113 | + await editor.click() |
| 114 | + await page.keyboard.press('ControlOrMeta+KeyA') |
| 115 | + await page.keyboard.type(`select length('hello');`) |
| 116 | + await page.getByTestId('sql-run-button').click() |
23 | 117 |
|
24 | | - // Wait until Running... is not visible |
25 | | - await expect(page.getByText('Running...')).not.toBeVisible() |
| 118 | + // verify the result is updated. |
| 119 | + await expect(page.getByRole('gridcell', { name: '5' })).toBeVisible({ |
| 120 | + timeout: 5000, |
| 121 | + }) |
| 122 | + }) |
| 123 | + |
| 124 | + test('destructive query would tripper a warning modal', async () => { |
| 125 | + await page.getByTestId('sql-editor-new-query-button').click() |
| 126 | + await page.getByRole('menuitem', { name: 'Create a new snippet' }).click() |
26 | 127 |
|
27 | | - // clear the editor |
| 128 | + // write some sql in the editor |
| 129 | + // This has to be done since the editor is not editable (input, textarea, etc.) |
| 130 | + await page.waitForTimeout(1000) |
| 131 | + const editor = page.getByRole('code').nth(0) |
28 | 132 | await editor.click() |
29 | 133 | await page.keyboard.press('ControlOrMeta+KeyA') |
30 | | - await page.keyboard.press('Backspace') |
| 134 | + await page.keyboard.type(`delete table 'test';`) |
| 135 | + await page.getByTestId('sql-run-button').click() |
31 | 136 |
|
32 | | - // verify the result |
33 | | - const result = page.getByRole('gridcell', { name: 'hello world' }) |
34 | | - await expect(result).toBeVisible() |
35 | | - }) |
36 | | -}) |
| 137 | + // verify warning modal is visible |
| 138 | + expect(page.getByRole('heading', { name: 'Potential issue detected with' })).toBeVisible() |
| 139 | + expect(page.getByText('Query has destructive')).toBeVisible() |
37 | 140 |
|
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`)) |
| 141 | + // reset test |
| 142 | + await page.getByRole('button', { name: 'Cancel' }).click() |
| 143 | + await page.waitForTimeout(500) |
| 144 | + await editor.click() |
| 145 | + await page.keyboard.press('ControlOrMeta+KeyA') |
| 146 | + await page.keyboard.press('Backspace') |
| 147 | + }) |
41 | 148 |
|
42 | | - const addButton = page.getByTestId('sql-editor-new-query-button') |
| 149 | + test('should create and load a new snippet', async ({ ref }) => { |
43 | 150 | const runButton = page.getByTestId('sql-run-button') |
44 | 151 | await page.getByRole('button', { name: 'Favorites' }).click() |
45 | 152 | 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 | 153 |
|
49 | 154 | // write some sql in the editor |
50 | | - await addButton.click() |
| 155 | + await page.getByTestId('sql-editor-new-query-button').click() |
51 | 156 | await page.getByRole('menuitem', { name: 'Create a new snippet' }).click() |
52 | | - |
53 | 157 | const editor = page.getByRole('code').nth(0) |
54 | 158 | await page.waitForTimeout(1000) |
55 | 159 | await editor.click() |
@@ -77,25 +181,52 @@ test.describe('SQL Snippets', () => { |
77 | 181 | await privateSnippet.getByText('Untitled query').click({ button: 'right' }) |
78 | 182 | await page.getByRole('menuitem', { name: 'Rename query', exact: true }).click() |
79 | 183 | await expect(page.getByRole('heading', { name: 'Rename' })).toBeVisible() |
80 | | - await page.getByRole('textbox', { name: 'Name' }).fill('test snippet') |
| 184 | + await page.getByRole('textbox', { name: 'Name' }).fill(pwTestQueryName) |
81 | 185 | 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() |
| 186 | + await page.waitForResponse( |
| 187 | + (response) => |
| 188 | + (response.url().includes(`projects/${ref}/content`) || |
| 189 | + response.url().includes('projects/default/content')) && |
| 190 | + response.request().method() === 'PUT' && |
| 191 | + response.status().toString().startsWith('2') |
| 192 | + ) |
| 193 | + await expect(privateSnippet.getByText(pwTestQueryName, { exact: true })).toBeVisible({ |
| 194 | + timeout: 50000, |
| 195 | + }) |
| 196 | + const privateSnippet2 = await privateSnippet.getByText(pwTestQueryName, { exact: true }) |
85 | 197 |
|
86 | 198 | // share with a team |
87 | 199 | await privateSnippet2.click({ button: 'right' }) |
88 | 200 | await page.getByRole('menuitem', { name: 'Share query with team' }).click() |
89 | | - await expect(page.getByRole('heading', { name: 'Confirm to share query: test' })).toBeVisible() |
| 201 | + await expect(page.getByRole('heading', { name: 'Confirm to share query' })).toBeVisible() |
90 | 202 | await page.getByRole('button', { name: 'Share query', exact: true }).click() |
| 203 | + await page.waitForResponse( |
| 204 | + (response) => |
| 205 | + (response.url().includes(`projects/${ref}/content`) || |
| 206 | + response.url().includes('projects/default/content')) && |
| 207 | + response.request().method() === 'PUT' && |
| 208 | + response.status().toString().startsWith('2') |
| 209 | + ) |
91 | 210 | const sharedSnippet = await page.getByLabel('project-level-snippets') |
92 | | - await expect(sharedSnippet).toContainText('test snippet') |
| 211 | + await expect(sharedSnippet).toContainText(pwTestQueryName) |
93 | 212 |
|
94 | 213 | // unshare a snippet |
95 | | - await sharedSnippet.getByText('test snippet').click({ button: 'right' }) |
| 214 | + await sharedSnippet.getByText(pwTestQueryName).click({ button: 'right' }) |
96 | 215 | await page.getByRole('menuitem', { name: 'Unshare query with team' }).click() |
97 | 216 | await expect(page.getByRole('heading', { name: 'Confirm to unshare query:' })).toBeVisible() |
98 | 217 | await page.getByRole('button', { name: 'Unshare query', exact: true }).click() |
99 | 218 | await expect(sharedSnippet).not.toBeVisible() |
| 219 | + |
| 220 | + // delete snippet (for non-local environment) |
| 221 | + if (!isCLI()) { |
| 222 | + deleteQuery(page, pwTestQueryName) |
| 223 | + |
| 224 | + await expect( |
| 225 | + page.getByText('Successfully deleted 1 query'), |
| 226 | + 'Delete confirmation toast should be visible' |
| 227 | + ).toBeVisible({ |
| 228 | + timeout: 50000, |
| 229 | + }) |
| 230 | + } |
100 | 231 | }) |
101 | 232 | }) |
0 commit comments