test: fix flaky table editor tests (#40323)

This commit is contained in:
Charis authored and GitHub committed 2025-11-11 16:05:39 -05:00
1 parent 6bb9791cb2
commit e08d1f7561
1 file changed
+52 -62
+52 -62
View File
@@ -1,6 +1,8 @@
import { expect, Locator, Page } from '@playwright/test'
import fs from 'fs'
import path from 'path'
import { isCLI } from '../utils/is-cli'
import { resetLocalStorage } from '../utils/reset-local-storage'
import { test } from '../utils/test'
import { toUrl } from '../utils/to-url'
import {
@@ -8,8 +10,6 @@ import {
waitForGridDataToLoad,
waitForTableToLoad,
} from '../utils/wait-for-response'
import { resetLocalStorage } from '../utils/reset-local-storage'
import { isCLI } from '../utils/is-cli'
import { waitForApiResponseWithTimeout } from '../utils/wait-for-response-with-timeout'
const tableNamePrefix = 'pw_table'
@@ -45,37 +45,46 @@ const createTable = async (page: Page, ref: string, tableName: string) => {
const nameInput = page.getByTestId('table-name-input')
await expect(nameInput).toBeVisible()
await nameInput.fill(tableName)
await expect(nameInput).toHaveValue(tableName)
await page.getByTestId('created_at-extra-options').click()
await page.getByText('Is Nullable').click()
await page.getByRole('checkbox', { name: 'Is Nullable' }).click()
await page.getByTestId('created_at-extra-options').click({ force: true })
await page.getByRole('button', { name: 'Add column' }).click()
await page.getByRole('textbox', { name: 'column_name' }).fill(columnName)
await page.getByText('Choose a column type...').click()
await page.getByRole('option', { name: 'text Variable-length' }).click()
await page.getByRole('button', { name: 'Save' }).click()
const createTablePromise = waitForApiResponseWithTimeout(page, (response) =>
response.url().includes('query?key=table-create')
)
// Wait specifically for tables list refresh instead of generic networkidle
await waitForApiResponseWithTimeout(page, (response) =>
const tablesPromise = waitForApiResponseWithTimeout(page, (response) =>
response.url().includes('tables?include_columns=true&included_schemas=public')
)
// wait for tables to load, we don't need to wait here cause this response may complete before the table creation.
await waitForApiResponseWithTimeout(page, (response) =>
const entitiesPromise = waitForApiResponseWithTimeout(page, (response) =>
response.url().includes('query?key=entity-types-public-')
)
await page.getByRole('button', { name: 'Save' }).click()
await Promise.all([createTablePromise, tablesPromise, entitiesPromise])
await expect(
page.getByRole('button', { name: `View ${tableName}`, exact: true }),
'Table should be visible after creation'
).toBeVisible({ timeout: 10000 })
).toBeVisible({ timeout: 15_000 })
}
const deleteTable = async (page: Page, ref: string, tableName: string) => {
const viewLocator = page.getByLabel(`View ${tableName}`)
if ((await viewLocator.count()) === 0) return
await viewLocator.nth(0).click()
await viewLocator.getByRole('button').nth(1).click({ force: true })
await viewLocator.locator('button[aria-haspopup="menu"]').click({ force: true })
await page.getByText('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 waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-delete-', { method: 'POST' })
await Promise.all([apiPromise, revalidatePromise])
}
const deleteEnumIfExist = async (page: Page, ref: string, enumName: string) => {
@@ -96,49 +105,36 @@ const deleteEnumIfExist = async (page: Page, ref: string, enumName: string) => {
}
test.describe.serial('table editor', () => {
let page: Page
test.beforeEach(async ({ ref }) => {
await resetLocalStorage(page, ref)
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
await waitForTableToLoad(page, ref)
})
test.beforeAll(async ({ browser, ref }) => {
page = await browser.newPage()
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 waitForTableToLoad(page, ref)
await loadPromise
// Delete all tables with prefix pw_table (ensure page is stable first)
const viewButtons = page.getByRole('button', { name: /^View / })
const names = (await viewButtons.allTextContents()).map((t) => t.replace(/^View\s+/, '').trim())
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 waitForTableToLoad(page, ref) // wait for table data to update
}
})
test.afterAll(async ({ ref }) => {
await resetLocalStorage(page, ref)
// Always navigate explicitly to editor and wait for tables to be loaded
test.beforeEach(async ({ page, ref }) => {
const loadPromise = waitForTableToLoad(page, ref)
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
await waitForTableToLoad(page, ref)
// Delete all tables with prefix pw_table
const viewButtons = page.getByRole('button', { name: /^View / })
const names = (await viewButtons.allTextContents()).map((t) => t.replace(/^View\s+/, '').trim())
const tablesToDelete = names.filter((tableName) => tableName.startsWith(tableNamePrefix))
for (const tableName of tablesToDelete) {
await deleteTable(page, ref, tableName)
await waitForTableToLoad(page, ref) // wait for table data to update
}
await loadPromise
})
test('sidebar actions works as expected', async ({ ref }) => {
test('sidebar actions works as expected', async ({ page, ref }) => {
const tableNameActions = 'pw_table_actions'
const tableNameActionsDuplicate = 'pw_table_actions_duplicate'
@@ -193,7 +189,7 @@ test.describe.serial('table editor', () => {
await deleteTable(page, ref, tableNameActions)
})
test('switching schemas work as expected', async ({ ref }) => {
test('switching schemas work as expected', async ({ page, ref }) => {
const authTableSso = 'identities'
const authTableMfa = 'mfa_factors'
@@ -212,7 +208,7 @@ test.describe.serial('table editor', () => {
await expect(page.getByLabel(`View ${authTableMfa}`)).toBeVisible()
})
test('should show rls accordingly', async ({ ref }) => {
test('should show rls accordingly', async ({ page, ref }) => {
const tableNameRlsEnabled = 'pw_table_rls_enabled'
const tableNameRlsDisabled = 'pw_table_rls_disabled'
@@ -226,22 +222,19 @@ test.describe.serial('table editor', () => {
await page.getByTestId('table-name-input').fill(tableNameRlsDisabled)
await page.getByLabel('Enable Row Level Security (').click()
await page.getByRole('button', { name: 'Confirm' }).click()
await page.getByRole('button', { name: 'Save' }).click()
await waitForApiResponse(
const apiPromise = waitForApiResponse(
page,
'pg-meta',
ref,
'tables?include_columns=false&included_schemas=public'
) // wait for table creation
await page.getByRole('button', { name: 'Save' }).click()
await apiPromise
await page.getByRole('button', { name: `View ${tableNameRlsDisabled}` }).click()
await expect(page.getByRole('button', { name: 'RLS disabled' })).toBeVisible()
// clear all tables
await deleteTable(page, ref, tableNameRlsEnabled)
await deleteTable(page, ref, tableNameRlsDisabled)
})
test('add enums and show enums on table', async ({ ref }) => {
test('add enums and show enums on table', async ({ page, ref }) => {
const tableNameEnum = 'pw_table_enum'
const columnNameEnum = 'pw_column_enum'
const enum_name = 'pw_enum'
@@ -317,7 +310,7 @@ test.describe.serial('table editor', () => {
await resetLocalStorage(page, ref)
})
test('Grid editor exporting works as expected', async ({ ref }) => {
test('Grid editor exporting works as expected', async ({ page, ref }) => {
const tableNameGridEditor = ' pw_table_grid_editor'
const tableNameUpdated = 'pw_table_updated'
const columnNameUpdated = 'pw_column_updated'
@@ -449,14 +442,9 @@ test.describe.serial('table editor', () => {
await page.getByRole('menuitem', { name: 'Export table via CLI' }).click()
await expect(page.getByRole('heading', { name: 'Export table data via CLI' })).toBeVisible()
await page.getByRole('button', { name: 'Close' }).first().click()
// Ensure all menus/dialogs are closed before continuing
await page.keyboard.press('Escape')
await page.keyboard.press('Escape')
await page.waitForTimeout(500)
})
test('filtering rows works as expected', async ({ ref }) => {
test('filtering rows works as expected', async ({ page, ref }) => {
const tableName = 'pw_table_filtering'
const colName = 'pw_column'
@@ -473,13 +461,14 @@ test.describe.serial('table editor', () => {
await page.getByTestId('table-editor-insert-new-row').click()
await page.getByRole('menuitem', { name: 'Insert row Insert a new row' }).click()
await page.getByTestId(`${colName}-input`).fill(value)
const apiPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', { method: 'POST' })
await page.getByTestId('action-bar-save-row').click()
await waitForApiResponse(page, 'pg-meta', ref, 'query?key=', { method: 'POST' })
await apiPromise
}
await page.getByRole('button', { name: 'Filter', exact: true }).click()
await page.getByRole('button', { name: 'Add filter' }).click()
await page.getByRole('button', { name: 'id' }).click()
await page.getByRole('button', { name: 'id', exact: true }).click()
await page.getByRole('menuitem', { name: colName }).click()
await page.getByRole('textbox', { name: 'Enter a value' }).fill('789')
await page.getByRole('button', { name: 'Apply filter' }).click()
@@ -493,25 +482,27 @@ test.describe.serial('table editor', () => {
await deleteTable(page, ref, tableName)
})
test('view table definition works as expected', async ({ ref }) => {
test('view table definition works as expected', async ({ page, ref }) => {
const tableName = 'pw_table_definition'
const colName = 'pw_column'
if (!page.url().includes('/editor')) {
const tableLoadPromise = waitForTableToLoad(page, ref)
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
await waitForTableToLoad(page, ref)
await tableLoadPromise
}
await createTable(page, ref, tableName)
await page.getByRole('button', { name: `View ${tableName}`, exact: true }).click()
await page.waitForURL(/\/editor\/\d+\?schema=public$/)
const apiPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-definition-')
await page.getByText('definition', { exact: true }).click()
await waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-definition-')
await apiPromise
await expect(page.locator('.view-lines')).toContainText(
`create table public.${tableName} ( id bigint generated by default as identity not null, created_at timestamp with time zone null default now(), ${colName} text null, constraint ${tableName}_pkey primary key (id)) TABLESPACE pg_default;`
)
await deleteTable(page, ref, tableName)
})
test('sorting rows works as expected', async ({ ref }) => {
test('sorting rows works as expected', async ({ page, ref }) => {
const tableName = 'pw_table_sorting'
const colName = 'pw_column'
@@ -552,7 +543,7 @@ test.describe.serial('table editor', () => {
await deleteTable(page, ref, tableName)
})
test('importing, pagination and large data actions works as expected', async ({ ref }) => {
test('importing, pagination and large data actions works as expected', async ({ page, ref }) => {
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
const tableNameDataActions = 'pw_table_data'
@@ -575,7 +566,6 @@ test.describe.serial('table editor', () => {
// importing 51 data via paste text
const filePath = path.join(__dirname, 'files', 'table-editor-import-paste.txt')
const fileContent = fs.readFileSync(filePath, 'utf-8')
await page.getByRole('button', { name: 'Close toast' }).first().click() // close toast, as paste text is behind toast
await page.getByTestId('table-editor-insert-new-row').click()
await page.getByRole('menuitem', { name: 'Import data from CSV' }).click()
await page.getByRole('tab', { name: 'Paste text' }).click()