mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix: remove flakiness with launch options and default timeouts (#41245)
* attempt to make table editor tests less flaky * updated race conditions for table editor * updated playwright config with optimized launch options * reduce workers * updated timeouts for tests * updated config to have a global timeout of 20 seconds * updated to be visible
This commit is contained in:
1 parent
0956558eea
commit
913371ca34
5 files changed
+120
-17
No files matched your search
@@ -60,7 +60,7 @@ test.describe('Logs', () => {
|
||||
|
||||
await expect(logsTable, {
|
||||
message: 'Logs table should be visible',
|
||||
}).toBeVisible({ timeout: 20000 })
|
||||
}).toBeVisible()
|
||||
|
||||
/**
|
||||
* Shows the logs data without errors
|
||||
@@ -73,10 +73,15 @@ test.describe('Logs', () => {
|
||||
* Can select and view log details
|
||||
*/
|
||||
const gridcells = page.getByText('Random event message')
|
||||
await expect(gridcells, {
|
||||
message: 'Grid cell should be visible before clicking',
|
||||
}).toBeVisible()
|
||||
await gridcells.click()
|
||||
|
||||
const tabPanel = page.getByTestId('log-selection')
|
||||
await expect(tabPanel).toBeVisible()
|
||||
await expect(tabPanel, {
|
||||
message: 'Log selection panel should be visible after clicking a log',
|
||||
}).toBeVisible()
|
||||
|
||||
// Assert known fixed values instead of extracting text
|
||||
await expect(tabPanel, {
|
||||
|
||||
@@ -59,7 +59,7 @@ test.describe.serial('Storage', () => {
|
||||
await expect(
|
||||
bucketRow.getByText('Public', { exact: true }),
|
||||
'Bucket should be marked as Public'
|
||||
).toBeVisible({ timeout: 10000 })
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('can edit bucket settings', async ({ page, ref }) => {
|
||||
@@ -91,7 +91,7 @@ test.describe.serial('Storage', () => {
|
||||
await expect(
|
||||
page.getByText('Public').first(),
|
||||
'Bucket should now be marked as Public'
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('can delete a bucket', async ({ page, ref }) => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { test } from '../utils/test.js'
|
||||
import { toUrl } from '../utils/to-url.js'
|
||||
import { waitForApiResponseWithTimeout } from '../utils/wait-for-response-with-timeout.js'
|
||||
import {
|
||||
createApiResponseWaiter,
|
||||
waitForApiResponse,
|
||||
waitForGridDataToLoad,
|
||||
waitForTableToLoad,
|
||||
@@ -72,7 +73,7 @@ const createTable = async (page: Page, ref: string, tableName: string) => {
|
||||
await expect(
|
||||
page.getByRole('button', { name: `View ${tableName}`, exact: true }),
|
||||
'Table should be visible after creation'
|
||||
).toBeVisible({ timeout: 15_000 })
|
||||
).toBeVisible()
|
||||
}
|
||||
|
||||
const deleteTable = async (page: Page, ref: string, tableName: string) => {
|
||||
@@ -493,8 +494,14 @@ testRunner('table editor', () => {
|
||||
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')
|
||||
const waitForFilterApply = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=table-rows-'
|
||||
)
|
||||
await page.getByRole('button', { name: 'Apply filter' }).click()
|
||||
await waitForGridDataToLoad(page, ref)
|
||||
await waitForFilterApply
|
||||
await page.waitForTimeout(500)
|
||||
await page.getByRole('button', { name: 'Filtered by 1 rule' }).click({ force: true })
|
||||
await expect(page.getByRole('gridcell', { name: '789' })).toBeVisible()
|
||||
@@ -551,8 +558,14 @@ testRunner('table editor', () => {
|
||||
await page.getByRole('button', { name: 'Sort', exact: true }).click()
|
||||
await page.getByRole('button', { name: 'Pick a column to sort by' }).click()
|
||||
await page.getByRole('menuitem', { name: colName }).click()
|
||||
const waitForSortingApply = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=table-rows-'
|
||||
)
|
||||
await page.getByRole('button', { name: 'Apply sorting' }).click()
|
||||
await waitForGridDataToLoad(page, ref)
|
||||
await waitForSortingApply
|
||||
await page.getByRole('button', { name: 'Sorted by 1 rule' }).click()
|
||||
|
||||
// Verify sorted row content asc lexicographically for strings
|
||||
@@ -580,8 +593,11 @@ testRunner('table editor', () => {
|
||||
await page.getByRole('tab', { name: 'Upload CSV' }).click()
|
||||
await page.setInputFiles('input[type="file"]', csvFilePath)
|
||||
await expect(page.getByText('A total of 50 rows will be')).toBeVisible()
|
||||
const waitForCsvInsert = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await page.getByRole('button', { name: 'Import data' }).click()
|
||||
await waitForApiResponse(page, 'pg-meta', ref, 'query?key=', { method: 'POST' }) // insert data
|
||||
await waitForCsvInsert // insert data
|
||||
await waitForGridDataToLoad(page, ref) // retrieve updated data
|
||||
await expect(page.getByText('50 records')).toBeVisible()
|
||||
|
||||
@@ -593,23 +609,33 @@ testRunner('table editor', () => {
|
||||
await page.getByRole('tab', { name: 'Paste text' }).click()
|
||||
await page.getByRole('textbox').fill(fileContent)
|
||||
await expect(page.getByText('A total of 51 rows will be')).toBeVisible()
|
||||
const waitForPasteInsert = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await page.getByRole('button', { name: 'Import data' }).click()
|
||||
await waitForApiResponse(page, 'pg-meta', ref, 'query?key=', { method: 'POST' }) // insert data
|
||||
await waitForPasteInsert // insert data
|
||||
await waitForGridDataToLoad(page, ref) // retrieve updated data
|
||||
await expect(page.getByText('101 records')).toBeVisible()
|
||||
|
||||
// test pagination (page 1 -> page 2)
|
||||
await expect(page.getByRole('gridcell', { name: 'value 7', exact: true })).toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'value 101', exact: true })).not.toBeVisible()
|
||||
const waitForPageChange = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-rows-')
|
||||
await page.getByLabel('Table grid footer').getByRole('button').nth(1).click()
|
||||
await waitForGridDataToLoad(page, ref) // retrieve next page data
|
||||
await waitForPageChange // retrieve next page data
|
||||
await expect(page.getByRole('gridcell', { name: 'value 7', exact: true })).not.toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'value 101', exact: true })).toBeVisible()
|
||||
|
||||
// change pagination size (100 -> 500)
|
||||
await page.getByRole('button', { name: 'rows' }).click()
|
||||
const waitForPaginationChange = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
ref,
|
||||
'query?key=table-rows-'
|
||||
)
|
||||
await page.getByRole('menuitem', { name: '500 rows' }).click()
|
||||
await waitForGridDataToLoad(page, ref) // retrieve updated pagination size data
|
||||
await waitForPaginationChange // retrieve updated pagination size data
|
||||
await expect(page.getByRole('gridcell', { name: 'value 7', exact: true })).toBeVisible()
|
||||
await page.getByRole('grid').evaluate((element) => {
|
||||
element.scrollTop = element.scrollHeight
|
||||
@@ -625,8 +651,11 @@ testRunner('table editor', () => {
|
||||
await page.getByRole('row', { name: 'value 3 to delete' }).getByRole('checkbox').click()
|
||||
await page.getByRole('button', { name: 'Delete 3 rows' }).click()
|
||||
await expect(page.getByText('delete the selected 3 rows')).toBeVisible()
|
||||
const waitForDeleteRows = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await page.getByRole('button', { name: 'Delete' }).click()
|
||||
await waitForApiResponse(page, 'pg-meta', ref, 'query?key=', { method: 'POST' }) // delete selected rows
|
||||
await waitForDeleteRows // delete selected rows
|
||||
await waitForGridDataToLoad(page, ref) // retrieve row data
|
||||
|
||||
// export selected rows when multiple rows action is selected
|
||||
@@ -705,9 +734,12 @@ testRunner('table editor', () => {
|
||||
// select all actions works (delete action)
|
||||
await page.getByRole('checkbox', { name: 'Select All' }).click()
|
||||
await page.getByRole('button', { name: 'Delete 98 rows' }).click()
|
||||
const waitForDeleteAllRows = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=', {
|
||||
method: 'POST',
|
||||
})
|
||||
await page.getByRole('button', { name: 'Delete' }).click()
|
||||
await expect(page.getByText('delete the selected 98 rows')).toBeVisible()
|
||||
await waitForApiResponse(page, 'pg-meta', ref, 'query?key=', { method: 'POST' }) // delete all rows
|
||||
await waitForDeleteAllRows // delete all rows
|
||||
await waitForGridDataToLoad(page, ref) // retrieve rows data
|
||||
await expect(page.getByRole('gridcell', { name: 'value 7' })).not.toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'value 101' })).not.toBeVisible()
|
||||
|
||||
@@ -39,6 +39,9 @@ export default defineConfig({
|
||||
forbidOnly: IS_CI,
|
||||
retries: IS_CI ? 5 : 0,
|
||||
maxFailures: 3,
|
||||
expect: {
|
||||
timeout: 20_000,
|
||||
},
|
||||
// Due to rate API rate limits run tests in serial mode on platform.
|
||||
fullyParallel: !env.IS_PLATFORM,
|
||||
workers: env.IS_PLATFORM ? 1 : 3,
|
||||
@@ -54,6 +57,69 @@ export default defineConfig({
|
||||
process.env.VERCEL_AUTOMATION_BYPASS_SELFHOSTED_STUDIO || 'false',
|
||||
'x-vercel-set-bypass-cookie': 'true',
|
||||
},
|
||||
launchOptions: {
|
||||
args: [
|
||||
// Security/Sandbox settings (required for CI environments)
|
||||
'--no-sandbox', // Disables Chrome's sandbox - required in Docker/CI where user namespaces aren't available
|
||||
'--disable-setuid-sandbox', // Alternative sandbox method - disabled for CI compatibility
|
||||
'--allow-insecure-localhost', // Allows tests against localhost with self-signed certificates
|
||||
|
||||
// Memory and resource management
|
||||
'--disable-dev-shm-usage', // Use /tmp instead of /dev/shm to avoid shared memory issues in containers
|
||||
'--js-flags=--max_old_space_size=4096', // Increase V8 heap size to 4GB to handle memory-intensive tests
|
||||
'--memory-pressure-off', // Prevents Chrome from killing tabs due to memory pressure in CI
|
||||
'--enable-low-end-device-mode', // Optimizes memory usage for resource-constrained environments
|
||||
|
||||
// GPU and rendering (disabled for headless/CI performance)
|
||||
'--disable-gpu', // Disables hardware GPU - not needed in headless mode
|
||||
'--disable-software-rasterizer', // Disables software-based rendering fallback
|
||||
|
||||
// Performance optimizations for testing
|
||||
'--disable-background-timer-throttling', // Prevents Chrome from throttling timers in background tabs
|
||||
'--disable-backgrounding-occluded-windows', // Keeps hidden windows running at full speed
|
||||
'--disable-renderer-backgrounding', // Prevents renderer processes from being deprioritized
|
||||
'--disable-ipc-flooding-protection', // Allows high-frequency IPC messages needed for automation
|
||||
|
||||
// Disable unnecessary features to reduce overhead
|
||||
'--disable-extensions', // Disables all browser extensions
|
||||
'--disable-sync', // Disables Chrome sync service
|
||||
'--disable-default-apps', // Prevents loading of default Chrome apps
|
||||
'--disable-component-update', // Disables automatic component updates during tests
|
||||
'--disable-background-networking', // Disables background network requests
|
||||
'--disable-features=TranslateUI', // Disables translation UI prompts
|
||||
'--disable-features=MediaRouter,site-per-process', // Disables Cast and site isolation for performance
|
||||
'--disable-features=HardwareMediaKeyHandling', // Disables hardware media key handling
|
||||
|
||||
// Disable monitoring and crash reporting
|
||||
'--disable-breakpad', // Disables crash reporting system
|
||||
'--disable-crash-reporter', // Disables crash reporter UI
|
||||
'--disable-hang-monitor', // Disables hang detection monitoring
|
||||
'--metrics-recording-only', // Disables metric uploads while still collecting them
|
||||
|
||||
// Disable security features not needed for testing
|
||||
'--disable-client-side-phishing-detection', // Disables phishing detection checks
|
||||
'--safebrowsing-disable-auto-update', // Disables safe browsing database updates
|
||||
'--disable-domain-reliability', // Disables domain reliability monitoring
|
||||
|
||||
// Disable user prompts and UI elements
|
||||
'--disable-popup-blocking', // Allows popups without user confirmation
|
||||
'--disable-prompt-on-repost', // Skips form resubmission confirmation dialogs
|
||||
'--no-first-run', // Skips first-run wizards and setup dialogs
|
||||
'--no-default-browser-check', // Prevents "set as default browser" prompts
|
||||
|
||||
// Process management
|
||||
'--no-zygote', // Disables zygote process for spawning renderers - reduces memory in single-use scenarios
|
||||
|
||||
// Headless mode configuration
|
||||
'--headless=new', // Uses new headless mode (more stable than old headless)
|
||||
'--window-size=1280,720', // Sets consistent viewport size for screenshot/visual consistency
|
||||
'--hide-scrollbars', // Hides scrollbars for cleaner screenshots
|
||||
'--mute-audio', // Prevents audio output during tests
|
||||
|
||||
// Network configuration
|
||||
'--enable-features=NetworkService,NetworkServiceInProcess', // Uses modern network service in-process for better performance
|
||||
],
|
||||
},
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ export const createBucket = async (
|
||||
await expect(
|
||||
page.getByRole('row').filter({ hasText: bucketName }),
|
||||
`Bucket ${bucketName} should be visible after creation`
|
||||
).toBeVisible({ timeout: 15_000 })
|
||||
).toBeVisible()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +147,7 @@ export const navigateToBucket = async (page: Page, ref: string, bucketName: stri
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit bucket' }),
|
||||
`Should be in bucket ${bucketName}`
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
).toBeVisible()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +171,7 @@ export const createFolder = async (page: Page, folderName: string) => {
|
||||
await expect(
|
||||
page.getByTitle(folderName),
|
||||
`Folder ${folderName} should be visible after creation`
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
).toBeVisible()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +217,7 @@ export const deleteItem = async (page: Page, itemName: string) => {
|
||||
await expect(
|
||||
page.getByTitle(itemName),
|
||||
`Item ${itemName} should not be visible after deletion`
|
||||
).not.toBeVisible({ timeout: 10_000 })
|
||||
).not.toBeVisible()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user