用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill debugging-troubleshooting命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
基于 SOC 职业分类
正在显示 SKILL.md
| name | debugging-troubleshooting |
| description | > Use when this capability is needed. |
Comprehensive guide to debugging Playwright tests — using Playwright Inspector, debug mode, trace viewer, video analysis, and systematic troubleshooting techniques.
# Run a specific test with the inspector
npx playwright test tests/checkout.spec.ts --debug
# Run all tests in debug mode
npx playwright test --debug
# Debug a specific test by title
npx playwright test -g "should complete checkout" --debug
test('debug this test with inspector', async ({ page }) => {
await page.goto('/products');
// Add page.pause() to stop execution and open inspector
await page.pause();
// Inspector opens here - you can:
// 1. Step through actions one at a time
// 2. Inspect the page's DOM
// 3. Try selectors in the Locator tab
// 4. View the action log
// 5. Resume or step over
await page.getByRole('button', { name: 'Add to Cart' }).click();
// Add another pause to inspect state after the click
await page.pause();
await expect(page.getByRole('alert')).toBeVisible();
});
// TIP 1: Use PWDEBUG environment variable
// PWDEBUG=1 npx playwright test
// This auto-opens inspector for ALL tests
// TIP 2: Use console mode in inspector
// PWDEBUG=console npx playwright test
// Opens browser DevTools console with Playwright helpers
// TIP 3: Pick locators interactively
// In the Inspector, click "Pick locator" then click any element
// on the page to get the recommended selector
# Run tests in headed mode (see the browser)
npx playwright test --headed
# Slow down actions for visual debugging
npx playwright test --headed --slow-mo=500
# Combine with specific test
npx playwright test tests/login.spec.ts --headed --slow-mo=1000
// playwright.config.ts - Debug-friendly config
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// Slow down actions by 200ms (useful for visual debugging)
launchOptions: {
slowMo: process.env.SLOW_MO ? parseInt(process.env.SLOW_MO) : 0,
},
// Keep browser open after test failure
headless: process.env.HEADED ? false : true,
},
});
// Temporarily focus on one test for debugging
test.only('debug: should add item to cart', async ({ page }) => {
// Set a longer timeout while debugging
test.setTimeout(0); // No timeout during debugging
await page.goto('/products');
// Log page URL for verification
console.log('Current URL:', page.url());
// Take a screenshot at a specific point
await page.screenshot({ path: 'debug-screenshot.png' });
await page.getByRole('button', { name: 'Add to Cart' }).click();
// Check what's on the page
const content = await page.textContent('body');
console.log('Page contains:', content?.substring(0, 500));
});
// IMPORTANT: Remove test.only before committing!
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// ✅ Recommended: Record trace on first retry
trace: 'on-first-retry',
// Other options:
// trace: 'on' - Always record (slower, more storage)
// trace: 'off' - Never record
// trace: 'retain-on-failure' - Keep only for failures
},
});
# Open the trace viewer with a trace file
npx playwright show-trace test-results/checkout-should-complete/trace.zip
# Open trace from HTML report
npx playwright show-report
# Then click on a failed test → "Traces" tab
// The trace viewer provides:
// 1. Timeline - Visual timeline of all test actions
// 2. Actions - Each step with before/after screenshots
// 3. Metadata - Test name, duration, status
// 4. Source - The test source code with current line highlighted
// 5. Network - All network requests and responses
// 6. Console - Browser console output
// 7. DOM - Snapshot of DOM at each step
// 8. Call - Detailed info about each Playwright call
// You can:
// - Click any action to see the page state
// - Filter network requests
// - Search the DOM snapshot
// - View request/response bodies
test('trace specific operations', async ({ page, context }) => {
// Start tracing for a specific section
await context.tracing.start({ screenshots: true, snapshots: true });
await page.goto('/checkout');
await page.getByLabel('Email').fill('user@example.com');
await page.getByRole('button', { name: 'Place Order' }).click();
// Stop and save trace
await context.tracing.stop({ path: 'traces/checkout-trace.zip' });
});
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// ✅ Record video only on failure (recommended)
video: 'on-first-retry',
// Video size configuration
video: {
mode: 'on-first-retry',
size: { width: 1280, height: 720 },
},
},
});
test('video will be saved on failure', async ({ page }, testInfo) => {
await page.goto('/products');
await page.getByRole('button', { name: 'Add to Cart' }).click();
// After test completes, video is available at:
// test-results/<test-name>/video.webm
// You can also access video path programmatically
const video = page.video();
if (video) {
const path = await video.path();
console.log('Video saved at:', path);
// Attach video to test report
await testInfo.attach('test-video', {
path: path,
contentType: 'video/webm',
});
}
});
// Use VIDEO when:
// - You need to see the full visual flow
// - Animations or transitions are involved
// - You want to share with non-technical stakeholders
// - CI/CD failure investigation
// Use TRACE when:
// - You need to inspect DOM state at each step
// - You need network request/response details
// - You need console output
// - You need step-by-step debugging
// - Trace is more detailed but larger files
// ✅ Best practice: Use both on first retry
// video: 'on-first-retry',
// trace: 'on-first-retry',
test('debug with screenshots', async ({ page }, testInfo) => {
await page.goto('/checkout');
// Capture state at key points
await page.screenshot({
path: 'debug/step-1-page-loaded.png',
fullPage: true,
});
await page.getByLabel('Email').fill('user@example.com');
// Capture after form fill
await page.screenshot({
path: 'debug/step-2-form-filled.png',
});
await page.getByRole('button', { name: 'Submit' }).click();
// Capture result
await page.screenshot({
path: 'debug/step-3-after-submit.png',
fullPage: true,
});
// Attach to report for easy access
await testInfo.attach('after-submit', {
body: await page.screenshot(),
contentType: 'image/png',
});
});
test('capture specific element state', async ({ page }) => {
await page.goto('/dashboard');
// Screenshot just the problematic component
const widget = page.getByTestId('revenue-widget');
await widget.screenshot({ path: 'debug/revenue-widget.png' });
// Screenshot with element highlighted
await page.evaluate(() => {
const el = document.querySelector('[data-testid="revenue-widget"]');
if (el) {
(el as HTMLElement).style.border = '3px solid red';
}
});
await page.screenshot({ path: 'debug/highlighted-widget.png' });
});
test('monitor console for errors', async ({ page }) => {
const consoleMessages: string[] = [];
const consoleErrors: string[] = [];
// Capture all console messages
page.on('console', (msg) => {
const text = `[${msg.type()}] ${msg.text()}`;
consoleMessages.push(text);
if (msg.type() === 'error') {
consoleErrors.push(text);
}
});
// Capture uncaught exceptions
page.on('pageerror', (error) => {
consoleErrors.push(`[uncaught] ${error.message}`);
});
await page.goto('/dashboard');
await page.getByRole('button', { name: 'Load Data' }).click();
// Assert no unexpected console errors
const unexpectedErrors = consoleErrors.filter(
(err) => !err.includes('Expected warning') // Filter known warnings
);
(unexpectedErrors, ).();
});
test('debug API calls', async ({ page }) => {
const apiCalls: Array<{ method: string; url: string; status: number }> = [];
// Log all API responses
page.on('response', (response) => {
if (response.url().includes('/api/')) {
apiCalls.push({
method: response.request().method(),
url: response.url(),
status: response.status(),
});
}
});
// Log failed requests
page.on('requestfailed', (request) => {
console.error(`Request failed: ${request.method()} ${request.url()}`);
console.error(`Reason: ${request.failure()?.errorText}`);
});
await page.goto('/products');
// Verify expected API calls were made
console.log(, .(apiCalls, , ));
(apiCalls.( call..())).();
});
test('wait for API before asserting', async ({ page }) => {
await page.goto('/dashboard');
// ✅ Good - Wait for specific API response before asserting
const [response] = await Promise.all([
page.waitForResponse('**/api/analytics'),
page.getByRole('button', { name: 'Refresh' }).click(),
]);
expect(response.status()).toBe(200);
const data = await response.json();
expect(data.totalRevenue).toBeGreaterThan(0);
});
// SYMPTOM: "Timeout waiting for selector"
// CAUSE: Selector doesn't match any element
// Debugging steps:
test('debug element not found', async ({ page }) => {
await page.goto('/products');
// Step 1: Check if page loaded correctly
console.log('Page URL:', page.url());
console.log('Page title:', await page.title());
// Step 2: Check if element exists at all
const count = await page.getByRole('button', { name: 'Add to Cart' }).count();
console.log('Matching elements:', count);
// Step 3: Check what's actually on the page
const buttons = await page.getByRole('button').allTextContents();
console.log('All buttons:', buttons);
// Step 4: Try a broader selector
const allText = await page.textContent('body');
console.log('Page text includes "Add":', allText?.());
});
// SYMPTOM: Test passes locally but fails in CI
// CAUSE: Race condition between UI update and assertion
// ✅ Fix: Use proper Playwright waiting
test('fix race condition', async ({ page }) => {
await page.goto('/products');
await page.getByRole('button', { name: 'Add to Cart' }).click();
// ✅ Wait for the specific response that triggers UI update
await page.waitForResponse('**/api/cart');
// ✅ Then assert with auto-waiting
await expect(page.getByRole('alert')).toHaveText('Added to cart');
});
// SYMPTOM: "Element is not attached to the DOM"
// CAUSE: Page re-rendered and element reference is stale
// ✅ Fix: Use locators (not element handles)
test('avoid stale element', async ({ page }) => {
await page.goto('/products');
// ✅ Locators always re-query the DOM
const addButton = page.getByRole('button', { name: 'Add to Cart' });
await addButton.click(); // Always finds the latest element
// ❌ Element handles can become stale
// const handle = await page.$('button.add-to-cart');
// await handle.click(); // May fail if DOM re-rendered
});
// SYMPTOM: "Target page, context or browser has been closed"
// CAUSE: Navigation triggered but test didn't wait for it
// ✅ Fix: Wait for navigation explicitly
test('handle navigation', async ({ page }) => {
await page.goto('/login');
// ✅ Wait for navigation triggered by form submit
await Promise.all([
page.waitForURL('**/dashboard'),
page.getByRole('button', { name: 'Login' }).click(),
]);
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
// playwright.config.ts - CI-specific configuration
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Retry failed tests in CI
retries: process.env.CI ? 2 : 0,
use: {
// Capture artifacts on failure in CI
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'on-first-retry',
},
// Output directory for artifacts
outputDir: 'test-results/',
// HTML report
reporter: process.env.CI
? [['html', { open: 'never' }], ['github']]
: [['html', { open: 'on-failure' }]],
});
# Download CI artifacts and view the HTML report
npx playwright show-report path/to/downloaded/report
# View trace from CI artifacts
npx playwright show-trace path/to/trace.zip
# Run specific failing test locally with CI-like settings
CI=true npx playwright test tests/failing-test.spec.ts --retries=0 --headed
// Add environment info to test output for comparison
test.beforeAll(async () => {
console.log('Environment:', {
ci: process.env.CI,
os: process.platform,
nodeVersion: process.version,
pwVersion: require('@playwright/test/package.json').version,
});
});
# Open a browser to test selectors
npx playwright open https://your-app.com
# Use the Locator picker in the toolbar to find selectors
# Type selectors in the console to test them:
# > page.getByRole('button', { name: 'Submit' })
# > page.getByLabel('Email')
test('debug selectors', async ({ page }) => {
await page.goto('/products');
// List all role-based matches
const buttons = page.getByRole('button');
console.log('Total buttons:', await buttons.count());
for (let i = 0; i < await buttons.count(); i++) {
console.log(`Button ${i}:`, await buttons.nth(i).textContent());
}
// Check if element is hidden vs missing
const submitBtn = page.getByRole('button', { name: 'Submit' });
const isVisible = await submitBtn.isVisible();
const count = await submitBtn.count();
console.log(`Submit button - count: ${count}, visible: ${isVisible}`);
// Use evaluate to inspect element properties
const element = page.getByTestId('product-card');
( element.() > ) {
classes = element.().();
.(, classes);
}
});
# Generate test code by interacting with the app
npx playwright codegen https://your-app.com
# Codegen will:
# 1. Open a browser with your app
# 2. Record your interactions
# 3. Generate Playwright test code with recommended selectors
# 4. You can copy selectors directly into your tests
Reproduce the failure
npx playwright test --headedGather evidence
test-results/npx playwright show-trace <trace.zip>Narrow the scope
page.pause() before the failing stepIdentify root cause
Apply the fix
test.only, , or Before considering a test complete, run it multiple times to confirm stability:
# Run a specific test 5 times to check for flakiness
npx playwright test -g "Should display product details" --repeat-each=5 --reporter=line
# Run a specific file 5 times
npx playwright test tests/checkout/cart.spec.ts --repeat-each=5 --reporter=line
# Use the provided hook script for convenience
./hooks/validate-test.sh "Should display product details"
./hooks/validate-test.sh "tests/checkout/cart.spec.ts"
Why 5 times?
// ✅ Good - Test is deterministic and passes every time
test('Should display product details', async ({ page }) => {
// Set up via API (not dependent on UI state)
const product = await createTestProduct(page, { name: 'Laptop' });
await page.goto(`/products/${product.id}`);
// Wait for specific API response, not arbitrary timeout
await page.waitForResponse('**/api/products/*');
await expect(page.getByRole('heading', { name: 'Laptop' })).toBeVisible();
});
// ❌ Bad - Test has timing issues that cause flakiness
test('display products', async ({ page }) => {
await page.goto('/products');
await page.waitForTimeout(2000); // Flaky!
await expect(page.getByText('Laptop')).toBeVisible();
});
# Debug a specific test
npx playwright test tests/my-test.spec.ts --debug
# Run headed with slow motion
npx playwright test --headed --slow-mo=500
# Generate test with codegen
npx playwright codegen http://localhost:3000
# View HTML report
npx playwright show-report
# View trace file
npx playwright show-trace test-results/trace.zip
# Run with full trace
npx playwright test --trace on
# List available tests
npx playwright test --list
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
page.pause()console.log