e2e-studio-tests by supabase/supabase
npx skills add https://github.com/supabase/supabase --skill e2e-studio-tests运行 Studio 应用程序的 Playwright 端到端测试。
测试必须从 e2e/studio 目录运行:
cd e2e/studio && pnpm run e2e
cd e2e/studio && pnpm run e2e -- features/cron-jobs.spec.ts
cd e2e/studio && pnpm run e2e -- --grep "test name pattern"
cd e2e/studio && pnpm run e2e -- --ui
IS_PLATFORM=false) 并行运行测试(3 个工作线程)广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
e2e/studio/features/*.spec.tsimport { test } from '../utils/test.js'page、ref 和其他辅助工具使用宽松的超时时间等待元素:
await expect(locator).toBeVisible({ timeout: 30000 })
为期望断言添加消息以便调试:
await expect(locator).toBeVisible({ timeout: 30000 }, '页面加载后元素应可见')
对于共享数据库状态的测试使用串行模式:
test.describe.configure({ mode: 'serial' })
带有可访问名称的 getByRole - 最健壮,测试可访问性
page.getByRole('button', { name: 'Save' })
page.getByRole('button', { name: 'Configure API privileges' })
getByTestId - 稳定,明确的测试钩子
page.getByTestId('table-editor-side-panel')
精确匹配的 getByText - 适用于唯一文本
page.getByText('Data API Access', { exact: true })
使用 CSS 的 locator - 谨慎使用,更脆弱
page.locator('[data-state="open"]')
XPath 选择器 - 对 DOM 变化很脆弱
// BAD
locator('xpath=ancestor::div[contains(@class, "space-y")]')
使用 locator('..') 进行父级遍历 - 当结构改变时会失效
// BAD
element.locator('..').getByRole('button')
在通用元素上使用宽泛的 filter({ hasText }) - 可能匹配多个元素
// BAD - 弹出层可能包含多个组合框
// 可以考虑缩小容器范围或更具体地筛选组合框
popover.getByRole('combobox')
当组件缺少良好的可访问名称时,在源代码中添加一个:
// 在 React 组件中
<Button aria-label="Configure API privileges">
<Settings />
</Button>
然后在测试中使用它:
page.getByRole('button', { name: 'Configure API privileges' })
将选择器限定在特定容器内,以避免匹配错误的元素:
// 良好 - 限定在侧边面板内
const sidePanel = page.getByTestId('table-editor-side-panel')
const toggle = sidePanel.getByRole('switch')
// 良好 - 找到唯一元素,然后从其开始限定范围
const popover = page.locator('[data-radix-popper-content-wrapper]')
const roleSection = popover.getByText('Anonymous (anon)', { exact: true })
waitForTimeout永远不要使用 waitForTimeout - 总是等待特定的东西:
// 错误
await page.waitForTimeout(1000)
// 良好 - 等待 UI 元素
await expect(page.getByText('Success')).toBeVisible()
// 良好 - 等待 API 响应
const apiPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-create')
await saveButton.click()
await apiPromise
// 良好 - 等待指示操作完成的提示
await expect(page.getByText('Table created successfully')).toBeVisible({ timeout: 15000 })
force: true与其强制点击隐藏元素,不如先让它们可见:
// 错误
await menuButton.click({ force: true })
// 良好 - 悬停以显示,然后点击
await tableRow.hover()
await expect(menuButton).toBeVisible()
await menuButton.click()
cd e2e/studio && pnpm exec playwright show-trace <path-to-trace.zip>
cd e2e/studio && pnpm exec playwright show-report
错误上下文文件保存在 test-results/ 目录中。
在本地调试时,使用 Playwright MCP 工具检查 UI。
关键区别在于 冷启动与热状态:
测试从空白数据库状态开始运行。每次测试运行都会重置数据库并启动新的容器。像 pg_cron 这样的扩展默认未启用。
pnpm dev:studio-local 进行本地开发当使用正在运行的开发服务器进行调试时,数据库可能已经包含之前运行的状态(扩展已启用,测试数据存在)。
在本地工作但在 CI 中失败的测试通常对现有状态有假设。
test.describe.configure({ mode: 'serial' }))运行 pnpm run e2e 时,测试框架会自动重置数据库。这与 CI 行为一致。
如果使用 pnpm dev:studio-local 进行 Playwright MCP 调试,请记住状态与 CI 不同。
pnpm run e2e -- features/<file>.spec.ts 在本地运行测试(冷启动)test-results/ 目录中的错误上下文pnpm dev:studio-local 并使用 Playwright MCP 工具每周安装量
163
仓库
GitHub 星标数
99.5K
首次出现
2026年2月3日
安全审计
安装于
opencode156
codex156
github-copilot155
gemini-cli154
kimi-cli150
amp150
Run Playwright end-to-end tests for the Studio application.
Tests must be run from the e2e/studio directory:
cd e2e/studio && pnpm run e2e
cd e2e/studio && pnpm run e2e -- features/cron-jobs.spec.ts
cd e2e/studio && pnpm run e2e -- --grep "test name pattern"
cd e2e/studio && pnpm run e2e -- --ui
IS_PLATFORM=false) runs tests in parallel (3 workers)e2e/studio/features/*.spec.tsimport { test } from '../utils/test.js'page, ref, and other helpersWait for elements with generous timeouts:
await expect(locator).toBeVisible({ timeout: 30000 })
Add messages to expects for debugging:
await expect(locator).toBeVisible({ timeout: 30000 }, 'Element should be visible after page load')
Use serial mode for tests sharing database state:
test.describe.configure({ mode: 'serial' })
getByRole with accessible name - Most robust, tests accessibility
page.getByRole('button', { name: 'Save' })
page.getByRole('button', { name: 'Configure API privileges' })
getByTestId - Stable, explicit test hooks
page.getByTestId('table-editor-side-panel')
getByText with exact match - Good for unique text
page.getByText('Data API Access', { exact: true })
locator with CSS - Use sparingly, more fragile
page.locator('[data-state="open"]')
XPath selectors - Fragile to DOM changes
// BAD
locator('xpath=ancestor::div[contains(@class, "space-y")]')
Parent traversal withlocator('..') - Breaks when structure changes
// BAD
element.locator('..').getByRole('button')
Broadfilter({ hasText }) on generic elements - May match multiple elements
// BAD - popover may have more than one combobox
// Could consider scoping down the container or filtering the combobox more specifically
popover.getByRole('combobox')
When a component lacks a good accessible name, add one in the source code:
// In the React component
<Button aria-label="Configure API privileges">
<Settings />
</Button>
Then use it in tests:
page.getByRole('button', { name: 'Configure API privileges' })
Scope selectors to specific containers to avoid matching wrong elements:
// Good - scoped to side panel
const sidePanel = page.getByTestId('table-editor-side-panel')
const toggle = sidePanel.getByRole('switch')
// Good - find unique element, then scope from there
const popover = page.locator('[data-radix-popper-content-wrapper]')
const roleSection = popover.getByText('Anonymous (anon)', { exact: true })
waitForTimeoutNever use waitForTimeout - always wait for something specific:
// BAD
await page.waitForTimeout(1000)
// GOOD - wait for UI element
await expect(page.getByText('Success')).toBeVisible()
// GOOD - wait for API response
const apiPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-create')
await saveButton.click()
await apiPromise
// GOOD - wait for toast indicating operation complete
await expect(page.getByText('Table created successfully')).toBeVisible({ timeout: 15000 })
force: true on clicksInstead of forcing clicks on hidden elements, make them visible first:
// BAD
await menuButton.click({ force: true })
// GOOD - hover to reveal, then click
await tableRow.hover()
await expect(menuButton).toBeVisible()
await menuButton.click()
cd e2e/studio && pnpm exec playwright show-trace <path-to-trace.zip>
cd e2e/studio && pnpm exec playwright show-report
Error context files are saved in the test-results/ directory.
Use Playwright MCP tools to inspect UI when debugging locally.
The key difference is cold start vs warm state :
Tests run from a blank database slate. Each test run resets the database and starts fresh containers. Extensions like pg_cron are NOT enabled by default.
pnpm dev:studio-localWhen debugging with a running dev server, the database may already have state from previous runs (extensions enabled, test data present).
Tests that work locally but fail in CI often have assumptions about existing state.
test.describe.configure({ mode: 'serial' }))The test framework automatically resets the database when running pnpm run e2e. This matches CI behavior.
If using pnpm dev:studio-local for Playwright MCP debugging, remember the state differs from CI.
pnpm run e2e -- features/<file>.spec.ts (cold start)test-results/ directorypnpm dev:studio-local and use Playwright MCP toolsWeekly Installs
163
Repository
GitHub Stars
99.5K
First Seen
Feb 3, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode156
codex156
github-copilot155
gemini-cli154
kimi-cli150
amp150