/** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import fs from 'fs'; import path from 'path'; import { test, expect } from './trace-cli-fixtures'; test.skip(({ mcpBrowser }) => mcpBrowser !== 'chrome', 'Chrome-only'); test('trace open shows metadata', async ({ traceFile, runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['open', traceFile]); expect(exitCode).toBe(0); expect(stdout).toContain('Browser:'); expect(stdout).toContain('chromium'); expect(stdout).toContain('Viewport:'); expect(stdout).toContain('800x600'); expect(stdout).toContain('Actions:'); expect(stdout).toContain('Pages:'); expect(stdout).toContain('Network:'); }); test('trace actions shows actions with ordinals', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['actions']); expect(exitCode).toBe(0); // Should have ordinal numbers expect(stdout).toMatch(/^\s+\d+\.\s/m); // Should have formatted action titles expect(stdout).toContain('Navigate'); expect(stdout).toContain('Click'); expect(stdout).toContain('Fill'); }); test('trace actions --grep filters actions', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['actions', '--grep', 'Click']); expect(exitCode).toBe(0); expect(stdout).toContain('Click'); expect(stdout).not.toContain('Navigate'); expect(stdout).not.toContain('Fill'); }); test('trace action displays action details', async ({ runTraceCli }) => { // First get an action ordinal from list const { stdout: listOutput } = await runTraceCli(['actions', '--grep', 'Navigate']); const match = listOutput.match(/^\s+(\d+)\.\s/m); expect(match).toBeTruthy(); const { stdout, exitCode } = await runTraceCli(['action', match![1]]); expect(exitCode).toBe(0); expect(stdout).toContain('Navigate'); expect(stdout).toContain('Time'); expect(stdout).toContain('start:'); expect(stdout).toContain('duration:'); expect(stdout).toContain('Parameters'); }); test('trace action reports available snapshots', async ({ runTraceCli }) => { const { stdout: listOutput } = await runTraceCli(['actions', '--grep', 'Click']); const match = listOutput.match(/^\s+(\d+)\.\s/m); expect(match).toBeTruthy(); const { stdout, exitCode } = await runTraceCli(['action', match![1]]); expect(exitCode).toBe(0); expect(stdout).toContain('Snapshots'); expect(stdout).toContain('available:'); }); test('trace action with invalid action ID', async ({ runTraceCli }) => { const { stderr, exitCode } = await runTraceCli(['action', '999999']); expect(exitCode).toBe(1); expect(stderr).toContain('not found'); }); test('trace requests shows requests with ordinals', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['requests']); expect(exitCode).toBe(0); expect(stdout).toContain('Method'); expect(stdout).toContain('Status'); expect(stdout).toContain('GET'); expect(stdout).toMatch(/\d+\./); }); test('trace requests --method filters', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['requests', '--method', 'GET']); expect(exitCode).toBe(0); expect(stdout).toContain('GET'); expect(stdout).not.toContain('POST'); }); test('trace request shows details', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['request', '1']); expect(exitCode).toBe(0); expect(stdout).toContain('General'); expect(stdout).toContain('status:'); expect(stdout).toContain('Request headers'); expect(stdout).toContain('Response headers'); }); test('trace request shows request and response body', async ({ traceCwd, runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['requests', '--grep', 'feedback']); expect(exitCode).toBe(0); expect(stdout).toContain('feedback'); const ordinal = stdout.match(/^\s+(\d+)\.\s/m)![1]; const { stdout: requestOutput, exitCode: requestExitCode } = await runTraceCli(['request', ordinal]); expect(requestExitCode).toBe(0); expect(requestOutput).toContain('Request body'); const requestBodyPath = path.join('.playwright-cli', 'trace', 'resources', '60e4c013c41de11280ed8ba99dd94144124a0c35.txt'); expect(requestOutput).toContain(' ' + requestBodyPath); expect(fs.readFileSync(path.join(traceCwd, requestBodyPath), 'utf-8')).toEqual('What a great product!'); expect(requestOutput).toContain('Response body'); expect(requestOutput).toContain('application/json'); const responseBodyPath = path.join('.playwright-cli', 'trace', 'resources', '030b61c2fb7042fb4fc0ef4287a4ae3c0d98ed68.json'); expect(requestOutput).toContain(' ' + responseBodyPath); expect(fs.readFileSync(path.join(traceCwd, responseBodyPath), 'utf-8')).toEqual(JSON.stringify({ received: true })); }); test('trace request with invalid ID', async ({ runTraceCli }) => { const { stderr, exitCode } = await runTraceCli(['request', '999999']); expect(exitCode).toBe(1); expect(stderr).toContain('not found'); }); test('trace console shows messages', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['console']); expect(exitCode).toBe(0); expect(stdout).toContain('info message'); expect(stdout).toContain('warning message'); expect(stdout).toContain('error message'); }); test('trace console --errors-only', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['console', '--errors-only']); expect(exitCode).toBe(0); expect(stdout).toContain('error message'); expect(stdout).not.toContain('info message'); }); test('trace errors', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['errors']); expect(exitCode).toBe(0); // Our test trace may or may not have errors, just verify it doesn't crash expect(stdout).toBeTruthy(); }); test('trace snapshot runs command on snapshot', async ({ runTraceCli }) => { const { stdout: listOutput } = await runTraceCli(['actions', '--grep', 'Navigate']); const match = listOutput.match(/^\s+(\d+)\.\s/m); expect(match).toBeTruthy(); const { stdout, exitCode } = await runTraceCli(['snapshot', match![1]]); expect(exitCode).toBe(0); expect(stdout).toBeTruthy(); }); test('trace snapshot --name before', async ({ runTraceCli }) => { const { stdout: listOutput } = await runTraceCli(['actions', '--grep', 'Click']); const match = listOutput.match(/^\s+(\d+)\.\s/m); expect(match).toBeTruthy(); const { stdout, exitCode } = await runTraceCli(['snapshot', '--name', 'before', match![1]]); expect(exitCode).toBe(0); expect(stdout).toBeTruthy(); }); test('trace snapshot resolves inner frames', async ({ runTraceCli }) => { const { stdout: listOutput } = await runTraceCli(['actions', '--grep', 'Click']); const ordinals = [...listOutput.matchAll(/^\s+(\d+)\.\s/gm)].map(m => m[1]); expect(ordinals.length).toBeGreaterThanOrEqual(2); const anchorClickOrdinal = ordinals[ordinals.length - 1]; const { stdout } = await runTraceCli(['snapshot', '--name', 'after', anchorClickOrdinal]); expect(stdout).toContain('Innermost'); }); test('trace snapshot replays sub-resource stylesheets from the archive', async ({ runTraceCli }) => { const { stdout: listOutput } = await runTraceCli(['actions', '--grep', 'Click']); const match = listOutput.match(/^\s+(\d+)\.\s/m); expect(match).toBeTruthy(); const ordinal = match![1]; const { stdout, exitCode } = await runTraceCli([ 'snapshot', '--name', 'before', ordinal, '--', 'eval', 'el => getComputedStyle(el).color', '#styled', ]); expect(exitCode).toBe(0); expect(stdout).toContain('rgb(255, 0, 0)'); }); test('trace screenshot saves image file', async ({ runTraceCli }, testInfo) => { const { stdout: listOutput } = await runTraceCli(['actions', '--grep', 'Navigate']); const match = listOutput.match(/^\s+(\d+)\.\s/m); expect(match).toBeTruthy(); const outPath = testInfo.outputPath('test-screenshot.png'); const { stdout, exitCode } = await runTraceCli(['screenshot', match![1], '-o', outPath]); // Screenshot may or may not be available depending on timing if (exitCode === 0) { expect(stdout).toContain('Screenshot saved to'); expect(fs.existsSync(outPath)).toBe(true); } }); test('trace close removes extracted trace', async ({ traceFile, runTraceCli }) => { const { exitCode } = await runTraceCli(['close']); expect(exitCode).toBe(0); // Subsequent commands should fail because trace is closed. const { stderr, exitCode: exitCode2 } = await runTraceCli(['actions']); expect(exitCode2).toBe(1); expect(stderr).toContain('No trace opened'); // Re-open for remaining tests. const { exitCode: exitCode3 } = await runTraceCli(['open', traceFile]); expect(exitCode3).toBe(0); }); test('trace open with .trace file creates link', async ({ traceFile, traceCwd, runTraceCli }) => { // Extract the zip using the CLI to get raw trace files. const extractCwd = path.join(path.dirname(traceFile), 'extracted'); fs.mkdirSync(extractCwd, { recursive: true }); const cliPath = path.resolve(__dirname, '../../packages/playwright-core/cli.js'); const { execFileSync } = require('child_process'); execFileSync(process.execPath, [cliPath, 'trace', 'open', traceFile], { cwd: extractCwd }); const extractDir = path.join(extractCwd, '.playwright-cli', 'trace'); const traceEntries = fs.readdirSync(extractDir).filter((f: string) => f.endsWith('.trace')); expect(traceEntries.length).toBeGreaterThan(0); const dotTraceFile = path.join(extractDir, traceEntries[0]); const { exitCode } = await runTraceCli(['open', dotTraceFile]); expect(exitCode).toBe(0); const linkFile = path.join(traceCwd, '.playwright-cli', 'trace', '.link'); expect(fs.existsSync(linkFile)).toBe(true); expect(fs.readFileSync(linkFile, 'utf-8')).toBe(dotTraceFile); const { stdout: actionsOutput, exitCode: actionsExitCode } = await runTraceCli(['actions']); expect(actionsExitCode).toBe(0); expect(actionsOutput).toContain('Navigate'); expect(actionsOutput).toContain('Click'); }); test('trace attachments lists attachments', async ({ runTraceCli }) => { const { stdout, exitCode } = await runTraceCli(['attachments']); expect(exitCode).toBe(0); // Our test trace has no attachments, just verify it doesn't crash expect(stdout).toBeTruthy(); });