Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

visual-debugging.md 17KB

5 дней назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. # Visual Debugging and Developer Ergonomics
  2. ## Principle
  3. Fast feedback loops and transparent debugging artifacts are critical for maintaining test reliability and developer confidence. Visual debugging tools (trace viewers, screenshots, videos, HAR files) turn cryptic test failures into actionable insights, reducing triage time from hours to minutes.
  4. ## Rationale
  5. **The Problem**: CI failures often provide minimal context—a timeout, a selector mismatch, or a network error—forcing developers to reproduce issues locally (if they can). This wastes time and discourages test maintenance.
  6. **The Solution**: Capture rich debugging artifacts **only on failure** to balance storage costs with diagnostic value. Modern tools like Playwright Trace Viewer, Cypress Debug UI, and HAR recordings provide interactive, time-travel debugging that reveals exactly what the test saw at each step.
  7. **Why This Matters**:
  8. - Reduces failure triage time by 80-90% (visual context vs logs alone)
  9. - Enables debugging without local reproduction
  10. - Improves test maintenance confidence (clear failure root cause)
  11. - Catches timing/race conditions that are hard to reproduce locally
  12. ## Pattern Examples
  13. ### Example 1: Playwright Trace Viewer Configuration (Production Pattern)
  14. **Context**: Capture traces for failures and retries so flaky runs can be compared directly. Prefer `retain-on-failure-and-retries` as the default policy so failed retries can be compared with passing runs.
  15. **Implementation**:
  16. ```typescript
  17. // playwright.config.ts
  18. import { defineConfig } from '@playwright/test';
  19. export default defineConfig({
  20. use: {
  21. // Visual debugging artifacts (best signal for flaky triage)
  22. trace: 'retain-on-failure-and-retries', // Keep every failed attempt
  23. screenshot: 'only-on-failure', // Not on success
  24. video: 'retain-on-failure', // Delete on pass
  25. // Context for debugging
  26. baseURL: process.env.BASE_URL || 'http://localhost:3000',
  27. // Timeout context
  28. actionTimeout: 15_000, // 15s for clicks/fills
  29. navigationTimeout: 30_000, // 30s for page loads
  30. },
  31. // CI-specific artifact retention
  32. reporter: [
  33. ['html', { outputFolder: 'playwright-report', open: 'never' }],
  34. ['junit', { outputFile: 'results.xml' }],
  35. ['list'], // Console output
  36. ],
  37. // Failure handling
  38. retries: process.env.CI ? 2 : 0, // Retry in CI to capture trace
  39. workers: process.env.CI ? 1 : undefined,
  40. });
  41. ```
  42. **Opening and Using Trace Viewer**:
  43. ```bash
  44. # After test failure in CI, download trace artifact
  45. # Then inspect locally:
  46. npx playwright trace open path/to/trace.zip
  47. # Filter to the failing expectation or action from the terminal
  48. npx playwright trace actions path/to/trace.zip --grep="expect"
  49. npx playwright trace action path/to/trace.zip 9
  50. npx playwright trace snapshot path/to/trace.zip 9 --name after
  51. # Or serve trace viewer:
  52. npx playwright show-report
  53. ```
  54. **Key Features to Use in Trace Viewer**:
  55. 1. **Timeline**: See each action (click, navigate, assertion) with timing
  56. 2. **Snapshots**: Hover over timeline to see DOM state at that moment
  57. 3. **Network Tab**: Inspect all API calls, headers, payloads, timing
  58. 4. **Console Tab**: View console.log/error messages
  59. 5. **Source Tab**: See test code with execution markers
  60. 6. **Metadata**: Browser, OS, test duration, screenshots
  61. **Why This Works**:
  62. - `retain-on-failure-and-retries` preserves enough history to compare the failing retry with a passing run
  63. - Screenshots + video give visual context without trace overhead
  64. - Interactive timeline makes timing issues obvious (race conditions, slow API)
  65. ---
  66. ### Example 2: HAR File Recording for Network Debugging
  67. **Context**: Capture all network activity for reproducible API debugging
  68. **Implementation**:
  69. ```typescript
  70. // tests/e2e/checkout-with-har.spec.ts
  71. import { test, expect } from '@playwright/test';
  72. import path from 'path';
  73. test.describe('Checkout Flow with HAR Recording', () => {
  74. test('should complete payment with full network capture', async ({ page, context }) => {
  75. // Start HAR recording BEFORE navigation
  76. await context.routeFromHAR(path.join(__dirname, '../fixtures/checkout.har'), {
  77. url: '**/api/**', // Only capture API calls
  78. update: true, // Update HAR if file exists
  79. });
  80. await page.goto('/checkout');
  81. // Interact with page
  82. await page.getByTestId('payment-method').selectOption('credit-card');
  83. await page.getByTestId('card-number').fill('4242424242424242');
  84. await page.getByTestId('submit-payment').click();
  85. // Wait for payment confirmation
  86. await expect(page.getByTestId('success-message')).toBeVisible();
  87. // HAR file saved to fixtures/checkout.har
  88. // Contains all network requests/responses for replay
  89. });
  90. });
  91. ```
  92. **Using HAR for Deterministic Mocking**:
  93. ```typescript
  94. // tests/e2e/checkout-replay-har.spec.ts
  95. import { test, expect } from '@playwright/test';
  96. import path from 'path';
  97. test('should replay checkout flow from HAR', async ({ page, context }) => {
  98. // Replay network from HAR (no real API calls)
  99. await context.routeFromHAR(path.join(__dirname, '../fixtures/checkout.har'), {
  100. url: '**/api/**',
  101. update: false, // Read-only mode
  102. });
  103. await page.goto('/checkout');
  104. // Same test, but network responses come from HAR file
  105. await page.getByTestId('payment-method').selectOption('credit-card');
  106. await page.getByTestId('card-number').fill('4242424242424242');
  107. await page.getByTestId('submit-payment').click();
  108. await expect(page.getByTestId('success-message')).toBeVisible();
  109. });
  110. ```
  111. **Key Points**:
  112. - **`update: true`** records new HAR or updates existing (for flaky API debugging)
  113. - **`update: false`** replays from HAR (deterministic, no real API)
  114. - Filter by URL pattern (`**/api/**`) to avoid capturing static assets
  115. - HAR files are human-readable JSON (easy to inspect/modify)
  116. **When to Use HAR**:
  117. - Debugging flaky tests caused by API timing/responses
  118. - Creating deterministic mocks for integration tests
  119. - Analyzing third-party API behavior (Stripe, Auth0)
  120. - Reproducing production issues locally (record HAR in staging)
  121. ---
  122. ### Example 3: Custom Artifact Capture (Console Logs + Network on Failure)
  123. **Context**: Capture additional debugging context automatically on test failure
  124. **Implementation**:
  125. ```typescript
  126. // playwright/support/fixtures/debug-fixture.ts
  127. import { test as base, type Request } from '@playwright/test';
  128. import fs from 'fs';
  129. import path from 'path';
  130. type DebugFixture = {
  131. captureDebugArtifacts: () => Promise<void>;
  132. };
  133. export const test = base.extend<DebugFixture>({
  134. captureDebugArtifacts: async ({ page }, use, testInfo) => {
  135. await use(async () => {
  136. // This function can be called manually in tests
  137. // But it also runs automatically on failure via afterEach
  138. });
  139. // After test completes, save artifacts if failed
  140. if (testInfo.status !== testInfo.expectedStatus) {
  141. const artifactDir = path.join(testInfo.outputDir, 'debug-artifacts');
  142. fs.mkdirSync(artifactDir, { recursive: true });
  143. const consoleLogs = (await page.consoleMessages()).map((msg) => `[${msg.type()} @ ${msg.timestamp().toISOString()}] ${msg.text()}`);
  144. const pageErrors = (await page.pageErrors()).map((error) => ({
  145. name: error.name,
  146. message: error.message,
  147. stack: error.stack,
  148. }));
  149. const networkRequests = await Promise.all(
  150. (await page.requests()).map(async (request: Request) => {
  151. const response = await request.response();
  152. return {
  153. url: request.url(),
  154. method: request.method(),
  155. status: response?.status() ?? 0,
  156. };
  157. }),
  158. );
  159. // Save console logs
  160. fs.writeFileSync(path.join(artifactDir, 'console.log'), consoleLogs.join('\n'), 'utf-8');
  161. // Save page errors
  162. fs.writeFileSync(path.join(artifactDir, 'page-errors.json'), JSON.stringify(pageErrors, null, 2), 'utf-8');
  163. // Save network summary
  164. fs.writeFileSync(path.join(artifactDir, 'network.json'), JSON.stringify(networkRequests, null, 2), 'utf-8');
  165. console.log(`Debug artifacts saved to: ${artifactDir}`);
  166. }
  167. },
  168. });
  169. ```
  170. **Usage in Tests**:
  171. ```typescript
  172. // tests/e2e/payment-with-debug.spec.ts
  173. import { test, expect } from '../support/fixtures/debug-fixture';
  174. test('payment flow captures debug artifacts on failure', async ({ page, captureDebugArtifacts }) => {
  175. await page.goto('/checkout');
  176. // Test will automatically capture console + network on failure
  177. await page.getByTestId('submit-payment').click();
  178. await expect(page.getByTestId('success-message')).toBeVisible({ timeout: 5000 });
  179. // If this fails, console.log and network.json saved automatically
  180. });
  181. ```
  182. **CI Integration (GitHub Actions)**:
  183. ```yaml
  184. # .github/workflows/e2e.yml
  185. name: E2E Tests with Artifacts
  186. on: [push, pull_request]
  187. jobs:
  188. test:
  189. runs-on: ubuntu-latest
  190. steps:
  191. - uses: actions/checkout@v4
  192. - uses: actions/setup-node@v4
  193. with:
  194. node-version-file: '.nvmrc'
  195. - name: Install dependencies
  196. run: npm ci
  197. - name: Run Playwright tests
  198. run: npm run test:e2e
  199. continue-on-error: true # Capture artifacts even on failure
  200. - name: Upload test artifacts on failure
  201. if: failure()
  202. uses: actions/upload-artifact@v4
  203. with:
  204. name: playwright-artifacts
  205. path: |
  206. test-results/
  207. playwright-report/
  208. retention-days: 30
  209. ```
  210. **Key Points**:
  211. - Fixtures automatically capture context without polluting test code
  212. - Only saves artifacts on failure (storage-efficient)
  213. - CI uploads artifacts for post-mortem analysis
  214. - `continue-on-error: true` ensures artifact upload even when tests fail
  215. ---
  216. ### Example 4: Accessibility Debugging Integration (axe-core in Trace Viewer)
  217. **Context**: Catch accessibility regressions during visual debugging
  218. **Implementation**:
  219. ```typescript
  220. // playwright/support/fixtures/a11y-fixture.ts
  221. import { test as base } from '@playwright/test';
  222. import AxeBuilder from '@axe-core/playwright';
  223. type A11yFixture = {
  224. checkA11y: () => Promise<void>;
  225. };
  226. export const test = base.extend<A11yFixture>({
  227. checkA11y: async ({ page }, use) => {
  228. await use(async () => {
  229. // Run axe accessibility scan
  230. const results = await new AxeBuilder({ page }).analyze();
  231. // Attach results to test report (visible in trace viewer)
  232. if (results.violations.length > 0) {
  233. console.log(`Found ${results.violations.length} accessibility violations:`);
  234. results.violations.forEach((violation) => {
  235. console.log(`- [${violation.impact}] ${violation.id}: ${violation.description}`);
  236. console.log(` Help: ${violation.helpUrl}`);
  237. });
  238. throw new Error(`Accessibility violations found: ${results.violations.length}`);
  239. }
  240. });
  241. },
  242. });
  243. ```
  244. **Usage with Visual Debugging**:
  245. ```typescript
  246. // tests/e2e/checkout-a11y.spec.ts
  247. import { test, expect } from '../support/fixtures/a11y-fixture';
  248. test('checkout page is accessible', async ({ page, checkA11y }) => {
  249. await page.goto('/checkout');
  250. // Verify page loaded
  251. await expect(page.getByRole('heading', { name: 'Checkout' })).toBeVisible();
  252. // Run accessibility check
  253. await checkA11y();
  254. // If violations found, test fails and trace captures:
  255. // - Screenshot showing the problematic element
  256. // - Console log with violation details
  257. // - Network tab showing any failed resource loads
  258. });
  259. ```
  260. **Trace Viewer Benefits**:
  261. - **Screenshot shows visual context** of accessibility issue (contrast, missing labels)
  262. - **Console tab shows axe-core violations** with impact level and helpUrl
  263. - **DOM snapshot** allows inspecting ARIA attributes at failure point
  264. - **Network tab** reveals if icon fonts or images failed (common a11y issue)
  265. **Cypress Equivalent**:
  266. ```javascript
  267. // cypress/support/commands.ts
  268. import 'cypress-axe';
  269. Cypress.Commands.add('checkA11y', (context = null, options = {}) => {
  270. cy.injectAxe(); // Inject axe-core
  271. cy.checkA11y(context, options, (violations) => {
  272. if (violations.length) {
  273. cy.task('log', `Found ${violations.length} accessibility violations`);
  274. violations.forEach((violation) => {
  275. cy.task('log', `- [${violation.impact}] ${violation.id}: ${violation.description}`);
  276. });
  277. }
  278. });
  279. });
  280. // tests/e2e/checkout-a11y.cy.ts
  281. describe('Checkout Accessibility', () => {
  282. it('should have no a11y violations', () => {
  283. cy.visit('/checkout');
  284. cy.injectAxe();
  285. cy.checkA11y();
  286. // On failure, Cypress UI shows:
  287. // - Screenshot of page
  288. // - Console log with violation details
  289. // - Network tab with API calls
  290. });
  291. });
  292. ```
  293. **Key Points**:
  294. - Accessibility checks integrate seamlessly with visual debugging
  295. - Violations are captured in trace viewer/Cypress UI automatically
  296. - Provides actionable links (helpUrl) to fix issues
  297. - Screenshots show visual context (contrast, layout)
  298. ---
  299. ### Example 5: Time-Travel Debugging Workflow (Playwright Inspector)
  300. **Context**: Debug tests interactively with step-through execution
  301. **Implementation**:
  302. ```typescript
  303. // tests/e2e/checkout-debug.spec.ts
  304. import { test, expect } from '@playwright/test';
  305. test('debug checkout flow step-by-step', async ({ page }) => {
  306. // Set breakpoint by uncommenting this:
  307. // await page.pause()
  308. await page.goto('/checkout');
  309. // Use Playwright Inspector to:
  310. // 1. Step through each action
  311. // 2. Inspect DOM at each step
  312. // 3. View network calls per action
  313. // 4. Take screenshots manually
  314. await page.getByTestId('payment-method').selectOption('credit-card');
  315. // Pause here to inspect form state
  316. // await page.pause()
  317. await page.getByTestId('card-number').fill('4242424242424242');
  318. await page.getByTestId('submit-payment').click();
  319. await expect(page.getByTestId('success-message')).toBeVisible();
  320. });
  321. ```
  322. **Running with Inspector**:
  323. ```bash
  324. # Open Playwright Inspector (GUI debugger)
  325. npx playwright test --debug
  326. # Or use headed mode with slowMo
  327. npx playwright test --headed --slow-mo=1000
  328. # Debug specific test
  329. npx playwright test checkout-debug.spec.ts --debug
  330. # Set environment variable for persistent debugging
  331. PWDEBUG=1 npx playwright test
  332. ```
  333. **Inspector Features**:
  334. 1. **Step-through execution**: Click "Next" to execute one action at a time
  335. 2. **DOM inspector**: Hover over elements to see selectors
  336. 3. **Network panel**: See API calls with timing
  337. 4. **Console panel**: View console.log output
  338. 5. **Pick locator**: Click element in browser to get selector
  339. 6. **Record mode**: Record interactions to generate test code
  340. **Common Debugging Patterns**:
  341. ```typescript
  342. // Pattern 1: Debug selector issues
  343. test('debug selector', async ({ page }) => {
  344. await page.goto('/dashboard');
  345. await page.pause(); // Inspector opens
  346. // In Inspector console, test selectors:
  347. // page.getByTestId('user-menu') ✅
  348. // page.getByRole('button', { name: 'Profile' }) ✅
  349. // page.locator('.btn-primary') ❌ (fragile)
  350. });
  351. // Pattern 2: Debug timing issues
  352. test('debug network timing', async ({ page }) => {
  353. await page.goto('/dashboard');
  354. // Set up network listener BEFORE interaction
  355. const responsePromise = page.waitForResponse('**/api/users');
  356. await page.getByTestId('load-users').click();
  357. await page.pause(); // Check network panel for timing
  358. const response = await responsePromise;
  359. expect(response.status()).toBe(200);
  360. });
  361. // Pattern 3: Debug state changes
  362. test('debug state mutation', async ({ page }) => {
  363. await page.goto('/cart');
  364. // Check initial state
  365. await expect(page.getByTestId('cart-count')).toHaveText('0');
  366. await page.pause(); // Inspect DOM
  367. await page.getByTestId('add-to-cart').click();
  368. await page.pause(); // Inspect DOM again (compare state)
  369. await expect(page.getByTestId('cart-count')).toHaveText('1');
  370. });
  371. ```
  372. **Key Points**:
  373. - `page.pause()` opens Inspector at that exact moment
  374. - Inspector shows DOM state, network activity, console at pause point
  375. - "Pick locator" feature helps find robust selectors
  376. - Record mode generates test code from manual interactions
  377. ---
  378. ## Visual Debugging Checklist
  379. Before deploying tests to CI, ensure:
  380. - [ ] **Artifact configuration**: `trace: 'retain-on-failure-and-retries'`, `screenshot: 'only-on-failure'`, `video: 'retain-on-failure'`
  381. - [ ] **CI artifact upload**: GitHub Actions/GitLab CI configured to upload `test-results/` and `playwright-report/`
  382. - [ ] **HAR recording**: Set up for flaky API tests (record once, replay deterministically)
  383. - [ ] **Custom debug fixtures**: Console logs + network summary captured on failure
  384. - [ ] **Accessibility integration**: axe-core violations visible in trace viewer
  385. - [ ] **Trace viewer docs**: README explains how to open traces locally (`npx playwright trace open`)
  386. - [ ] **Inspector workflow**: Document `--debug` flag for interactive debugging
  387. - [ ] **Storage optimization**: Artifacts deleted after 30 days (CI retention policy)
  388. ## Integration Points
  389. - **Used in workflows**: `*framework` (initial setup), `*ci` (artifact upload), `*test-review` (validate artifact config)
  390. - **Related fragments**: `playwright-config.md` (artifact configuration), `ci-burn-in.md` (CI artifact upload), `test-quality.md` (debugging best practices)
  391. - **Tools**: Playwright Trace Viewer, Cypress Debug UI, axe-core, HAR files
  392. _Source: Playwright official docs, Murat testing philosophy (visual debugging manifesto), enterprise production debugging patterns_