diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 33419387c7..213928e2e7 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -239,7 +239,10 @@ export default defineConfig({ Metadata contains key-value pairs to be included in the report. For example, HTML report will display it as key-value pairs, and JSON report will include metadata serialized as json. -Providing `'git.commit.info': {}` property will populate it with the git commit details. This is useful for CI/CD environments. +* Providing `gitCommit: 'generate'` property will populate it with the git commit details. +* Providing `gitDiff: 'generate'` property will populate it with the git diff details. + +On selected CI providers, both will be generated automatically. Specifying values will prevent the automatic generation. **Usage** diff --git a/packages/html-reporter/src/metadataView.tsx b/packages/html-reporter/src/metadataView.tsx index a7db657b4d..b210e844c8 100644 --- a/packages/html-reporter/src/metadataView.tsx +++ b/packages/html-reporter/src/metadataView.tsx @@ -20,32 +20,10 @@ import './common.css'; import './theme.css'; import './metadataView.css'; import type { Metadata } from '@playwright/test'; -import type { GitCommitInfo } from '@testIsomorphic/types'; +import type { CIInfo, GitCommitInfo, MetadataWithCommitInfo } from '@testIsomorphic/types'; import { CopyToClipboardContainer } from './copyToClipboard'; import { linkifyText } from '@web/renderUtils'; -type MetadataEntries = [string, unknown][]; - -export const MetadataContext = React.createContext([]); - -export function MetadataProvider({ metadata, children }: React.PropsWithChildren<{ metadata: Metadata }>) { - const entries = React.useMemo(() => { - // TODO: do not plumb actualWorkers through metadata. - return Object.entries(metadata).filter(([key]) => key !== 'actualWorkers'); - }, [metadata]); - - return {children}; -} - -export function useMetadata() { - return React.useContext(MetadataContext); -} - -export function useGitCommitInfo() { - const metadataEntries = useMetadata(); - return metadataEntries.find(([key]) => key === 'git.commit.info')?.[1] as GitCommitInfo | undefined; -} - class ErrorBoundary extends React.Component, { error: Error | null, errorInfo: React.ErrorInfo | null }> { override state: { error: Error | null, errorInfo: React.ErrorInfo | null } = { error: null, @@ -72,23 +50,22 @@ class ErrorBoundary extends React.Component, { error } } -export const MetadataView = () => { - return ; +export const MetadataView: React.FC<{ metadata: Metadata }> = params => { + return ; }; -const InnerMetadataView = () => { - const metadataEntries = useMetadata(); - const gitCommitInfo = useGitCommitInfo(); - const entries = metadataEntries.filter(([key]) => key !== 'git.commit.info'); - if (!gitCommitInfo && !entries.length) - return null; +const InnerMetadataView: React.FC<{ metadata: Metadata }> = params => { + const commitInfo = params.metadata as MetadataWithCommitInfo; + const otherEntries = Object.entries(params.metadata).filter(([key]) => !ignoreKeys.has(key)); + const hasMetadata = commitInfo.ci || commitInfo.gitCommit || otherEntries.length > 0; + if (!hasMetadata) + return; return
- {gitCommitInfo && <> - - {entries.length > 0 &&
} - } + {commitInfo.ci && } + {commitInfo.gitCommit && } + {otherEntries.length > 0 && (commitInfo.gitCommit || commitInfo.ci) &&
}
- {entries.map(([propertyName, value]) => { + {otherEntries.map(([propertyName, value]) => { const valueString = typeof value !== 'object' || value === null || value === undefined ? String(value) : JSON.stringify(value); const trimmedValue = valueString.length > 1000 ? valueString.slice(0, 1000) + '\u2026' : valueString; return ( @@ -104,20 +81,24 @@ const InnerMetadataView = () => {
; }; -const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => { - const email = info.revision?.email ? ` <${info.revision?.email}>` : ''; - const author = `${info.revision?.author || ''}${email}`; +const CiInfoView: React.FC<{ info: CIInfo }> = ({ info }) => { + const link = info.commitHref; + return
+ +
; +}; - let subject = info.revision?.subject || ''; - let link = info.revision?.link; +const GitCommitInfoView: React.FC<{ link?: string, info: GitCommitInfo }> = ({ link, info }) => { + const subject = info.subject; + const email = ` <${info.author.email}>`; + const author = `${info.author.name}${email}`; + const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info.committer.time); + const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info.committer.time); - if (info.pull_request?.link && info.pull_request?.title) { - subject = info.pull_request?.title; - link = info.pull_request?.link; - } - - const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info.revision?.timestamp); - const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info.revision?.timestamp); return
{link ? ( @@ -131,12 +112,13 @@ const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
{author} on {shortTimestamp} - {info.ci?.link && ( - <> - ยท - Logs - - )}
; }; + +const ignoreKeys = new Set(['ci', 'gitCommit', 'gitDiff', 'actualWorkers']); + +export const isMetadataEmpty = (metadata: MetadataWithCommitInfo): boolean => { + const otherEntries = Object.entries(metadata).filter(([key]) => !ignoreKeys.has(key)); + return !metadata.ci && !metadata.gitCommit && !otherEntries.length; +}; diff --git a/packages/html-reporter/src/reportContext.tsx b/packages/html-reporter/src/reportContext.tsx new file mode 100644 index 0000000000..0ea8ab1e50 --- /dev/null +++ b/packages/html-reporter/src/reportContext.tsx @@ -0,0 +1,29 @@ +/* + 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 * as React from 'react'; +import type { HTMLReport } from './types'; + + +const HTMLReportContext = React.createContext(undefined); + +export function HTMLReportContextProvider({ report, children }: React.PropsWithChildren<{ report: HTMLReport | undefined }>) { + return {children}; +} + +export function useHTMLReport() { + return React.useContext(HTMLReportContext); +} diff --git a/packages/html-reporter/src/reportView.tsx b/packages/html-reporter/src/reportView.tsx index baf85f32a8..12b97584f3 100644 --- a/packages/html-reporter/src/reportView.tsx +++ b/packages/html-reporter/src/reportView.tsx @@ -26,7 +26,7 @@ import './reportView.css'; import { TestCaseView } from './testCaseView'; import { TestFilesHeader, TestFilesView } from './testFilesView'; import './theme.css'; -import { MetadataProvider } from './metadataView'; +import { HTMLReportContextProvider } from './reportContext'; declare global { interface Window { @@ -73,7 +73,7 @@ export const ReportView: React.FC<{ return result; }, [report, filter]); - return
+ return
{report?.json() && } @@ -89,7 +89,7 @@ export const ReportView: React.FC<{ {!!report && }
-
; +
; }; const TestCaseViewLoader: React.FC<{ diff --git a/packages/html-reporter/src/testErrorView.tsx b/packages/html-reporter/src/testErrorView.tsx index 9134f082d0..3d253459ba 100644 --- a/packages/html-reporter/src/testErrorView.tsx +++ b/packages/html-reporter/src/testErrorView.tsx @@ -21,9 +21,14 @@ import type { ImageDiff } from '@web/shared/imageDiffView'; import { ImageDiffView } from '@web/shared/imageDiffView'; import type { TestResult } from './types'; import { fixTestPrompt } from '@web/components/prompts'; -import { useGitCommitInfo } from './metadataView'; +import { useHTMLReport } from './reportContext'; +import type { MetadataWithCommitInfo } from '@playwright/isomorphic/types'; -export const TestErrorView: React.FC<{ error: string; testId?: string; result?: TestResult }> = ({ error, testId, result }) => { +export const TestErrorView: React.FC<{ + error: string; + testId?: string; + result?: TestResult +}> = ({ error, testId, result }) => { return (
@@ -47,12 +52,13 @@ const PromptButton: React.FC<{ error: string; result?: TestResult; }> = ({ error, result }) => { - const gitCommitInfo = useGitCommitInfo(); + const report = useHTMLReport(); + const commitInfo = report?.metadata as MetadataWithCommitInfo | undefined; const prompt = React.useMemo(() => fixTestPrompt( error, - gitCommitInfo?.pull_request?.diff ?? gitCommitInfo?.revision?.diff, + commitInfo?.gitDiff, result?.attachments.find(a => a.name === 'pageSnapshot')?.body - ), [gitCommitInfo, result, error]); + ), [commitInfo, result, error]); const [copied, setCopied] = React.useState(false); diff --git a/packages/html-reporter/src/testFilesView.tsx b/packages/html-reporter/src/testFilesView.tsx index 49e2233669..4b2c48ae1d 100644 --- a/packages/html-reporter/src/testFilesView.tsx +++ b/packages/html-reporter/src/testFilesView.tsx @@ -22,7 +22,7 @@ import { msToString } from './utils'; import { AutoChip } from './chip'; import { TestErrorView } from './testErrorView'; import * as icons from './icons'; -import { MetadataView, useMetadata } from './metadataView'; +import { isMetadataEmpty, MetadataView } from './metadataView'; export const TestFilesView: React.FC<{ tests: TestFileSummary[], @@ -67,13 +67,12 @@ export const TestFilesHeader: React.FC<{ metadataVisible: boolean, toggleMetadataVisible: () => void, }> = ({ report, filteredStats, metadataVisible, toggleMetadataVisible }) => { - const metadataEntries = useMetadata(); if (!report) return null; return <>
- {metadataEntries.length > 0 &&
+ {!isMetadataEmpty(report.metadata) &&
{metadataVisible ? icons.downArrow() : icons.rightArrow()}Metadata
} {report.projectNames.length === 1 && !!report.projectNames[0] &&
Project: {report.projectNames[0]}
} @@ -83,7 +82,7 @@ export const TestFilesHeader: React.FC<{
{report ? new Date(report.startTime).toLocaleString() : ''}
Total time: {msToString(report.duration ?? 0)}
- {metadataVisible && } + {metadataVisible && } {!!report.errors.length && {report.errors.map((error, index) => )} } diff --git a/packages/playwright/src/isomorphic/types.d.ts b/packages/playwright/src/isomorphic/types.d.ts index 2d54911c6e..2f0b85e1f0 100644 --- a/packages/playwright/src/isomorphic/types.d.ts +++ b/packages/playwright/src/isomorphic/types.d.ts @@ -14,23 +14,40 @@ * limitations under the License. */ -export interface GitCommitInfo { - revision?: { - id?: string; - author?: string; - email?: string; - subject?: string; - timestamp?: number; - link?: string; - diff?: string; - }, - pull_request?: { - link?: string; - diff?: string; - base?: string; - title?: string; - }, - ci?: { - link?: string; - } -} +export type GitCommitInfo = { + shortHash: string; + hash: string; + subject: string; + body: string; + author: { + name: string; + email: string; + time: number; + }; + committer: { + name: string; + email: string + time: number; + }; + branch: string; +}; + +export type CIInfo = { + commitHref: string; + buildHref?: string; + commitHash?: string; + baseHash?: string; + branch?: string; +}; + +export type UserMetadataWithCommitInfo = { + ci?: CIInfo; + gitCommit?: GitCommitInfo | 'generate'; + gitDiff?: string | 'generate'; +}; + +export type MetadataWithCommitInfo = { + ci?: CIInfo; + gitCommit?: GitCommitInfo; + gitDiff?: string; +}; diff --git a/packages/playwright/src/plugins/gitCommitInfoPlugin.ts b/packages/playwright/src/plugins/gitCommitInfoPlugin.ts index 20945f6332..a80ef3b705 100644 --- a/packages/playwright/src/plugins/gitCommitInfoPlugin.ts +++ b/packages/playwright/src/plugins/gitCommitInfoPlugin.ts @@ -14,119 +14,139 @@ * limitations under the License. */ -import fs from 'fs'; - -import { createGuid, spawnAsync } from 'playwright-core/lib/utils'; +import { spawnAsync } from 'playwright-core/lib/utils'; import type { TestRunnerPlugin } from './'; import type { FullConfig } from '../../types/testReporter'; import type { FullConfigInternal } from '../common/config'; -import type { GitCommitInfo } from '../isomorphic/types'; +import type { GitCommitInfo, CIInfo, UserMetadataWithCommitInfo } from '../isomorphic/types'; -const GIT_OPERATIONS_TIMEOUT_MS = 1500; +const GIT_OPERATIONS_TIMEOUT_MS = 3000; export const addGitCommitInfoPlugin = (fullConfig: FullConfigInternal) => { - const commitProperty = fullConfig.config.metadata['git.commit.info']; - if (commitProperty && typeof commitProperty === 'object' && Object.keys(commitProperty).length === 0) - fullConfig.plugins.push({ factory: gitCommitInfo }); + fullConfig.plugins.push({ factory: gitCommitInfoPlugin }); }; -export const gitCommitInfo = (options?: GitCommitInfoPluginOptions): TestRunnerPlugin => { +type GitCommitInfoPluginOptions = { + directory?: string; +}; + +export const gitCommitInfoPlugin = (options?: GitCommitInfoPluginOptions): TestRunnerPlugin => { return { name: 'playwright:git-commit-info', setup: async (config: FullConfig, configDir: string) => { - const commitInfo = await linksFromEnv(); - await enrichStatusFromCLI(options?.directory || configDir, commitInfo); - config.metadata = config.metadata || {}; - config.metadata['git.commit.info'] = commitInfo; + const metadata = config.metadata as UserMetadataWithCommitInfo; + const ci = ciInfo(); + if (!metadata.ci && ci) + metadata.ci = ci; + + if ((ci && !metadata.gitCommit) || metadata.gitCommit === 'generate') { + const git = await gitCommitInfo(options?.directory || configDir).catch(e => { + // eslint-disable-next-line no-console + console.error('Failed to get git commit info', e); + }); + if (git) + metadata.gitCommit = git; + } + + if ((ci && !metadata.gitDiff) || metadata.gitDiff === 'generate') { + const diffResult = await gitDiff(options?.directory || configDir, ci).catch(e => { + // eslint-disable-next-line no-console + console.error('Failed to get git diff', e); + }); + if (diffResult) + metadata.gitDiff = diffResult; + } }, }; }; -interface GitCommitInfoPluginOptions { - directory?: string; +function ciInfo(): CIInfo | undefined { + if (process.env.GITHUB_ACTIONS) { + return { + commitHref: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`, + buildHref: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`, + commitHash: process.env.GITHUB_SHA, + baseHash: process.env.GITHUB_BASE_REF, + branch: process.env.GITHUB_REF_NAME, + }; + } + + if (process.env.GITLAB_CI) { + return { + commitHref: `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`, + buildHref: process.env.CI_JOB_URL, + commitHash: process.env.CI_COMMIT_SHA, + baseHash: process.env.CI_COMMIT_BEFORE_SHA, + branch: process.env.CI_COMMIT_REF_NAME, + }; + } + + if (process.env.JENKINS_URL && process.env.BUILD_URL) { + return { + commitHref: process.env.BUILD_URL, + commitHash: process.env.GIT_COMMIT, + baseHash: process.env.GIT_PREVIOUS_COMMIT, + branch: process.env.GIT_BRANCH, + }; + } + + // Open to PRs. } -async function linksFromEnv(): Promise { - const out: GitCommitInfo = {}; - // Jenkins: https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables - if (process.env.BUILD_URL) { - out.ci = out.ci || {}; - out.ci.link = process.env.BUILD_URL; - } - // GitLab: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - if (process.env.CI_PROJECT_URL && process.env.CI_COMMIT_SHA) { - out.revision = out.revision || {}; - out.revision.link = `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`; - } - if (process.env.CI_JOB_URL) { - out.ci = out.ci || {}; - out.ci.link = process.env.CI_JOB_URL; - } - // GitHub: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables - if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_SHA) { - out.revision = out.revision || {}; - out.revision.link = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`; - } - if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID) { - out.ci = out.ci || {}; - out.ci.link = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; - } - if (process.env.GITHUB_EVENT_PATH) { - try { - const json = JSON.parse(await fs.promises.readFile(process.env.GITHUB_EVENT_PATH, 'utf8')); - if (json.pull_request) { - out.pull_request = out.pull_request || {}; - out.pull_request.title = json.pull_request.title; - out.pull_request.link = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${json.pull_request.number}`; - out.pull_request.base = json.pull_request.base.ref; - } - } catch { - } - } - return out; -} - -async function enrichStatusFromCLI(gitDir: string, commitInfo: GitCommitInfo) { - const separator = `:${createGuid().slice(0, 4)}:`; +async function gitCommitInfo(gitDir: string): Promise { + const separator = `---786eec917292---`; + const tokens = [ + '%H', // commit hash + '%h', // abbreviated commit hash + '%s', // subject + '%B', // raw body (unwrapped subject and body) + '%an', // author name + '%ae', // author email + '%at', // author date, UNIX timestamp + '%cn', // committer name + '%ce', // committer email + '%ct', // committer date, UNIX timestamp + '', // branch + ]; const commitInfoResult = await spawnAsync( - 'git', - ['show', '-s', `--format=%H${separator}%s${separator}%an${separator}%ae${separator}%ct`, 'HEAD'], - { stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS } + `git log -1 --pretty=format:"${tokens.join(separator)}" && git rev-parse --abbrev-ref HEAD`, [], + { stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS, shell: true } ); if (commitInfoResult.code) - return; + return undefined; const showOutput = commitInfoResult.stdout.trim(); - const [id, subject, author, email, rawTimestamp] = showOutput.split(separator); - let timestamp: number = Number.parseInt(rawTimestamp, 10); - timestamp = Number.isInteger(timestamp) ? timestamp * 1000 : 0; + const [hash, shortHash, subject, body, authorName, authorEmail, authorTime, committerName, committerEmail, committerTime, branch] = showOutput.split(separator); - commitInfo.revision = { - ...commitInfo.revision, - id, - author, - email, + return { + shortHash, + hash, subject, - timestamp, + body, + author: { + name: authorName, + email: authorEmail, + time: +authorTime * 1000, + }, + committer: { + name: committerName, + email: committerEmail, + time: +committerTime * 1000, + }, + branch: branch.trim(), }; - - const diffLimit = 1_000_000; // 1MB - if (commitInfo.pull_request?.base) { - const pullDiffResult = await spawnAsync( - 'git', - ['diff', commitInfo.pull_request?.base], - { stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS } - ); - if (!pullDiffResult.code) - commitInfo.pull_request!.diff = pullDiffResult.stdout.substring(0, diffLimit); - } else { - const diffResult = await spawnAsync( - 'git', - ['diff', 'HEAD~1'], - { stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS } - ); - if (!diffResult.code) - commitInfo.revision!.diff = diffResult.stdout.substring(0, diffLimit); - } +} + +async function gitDiff(gitDir: string, ci?: CIInfo): Promise { + const diffLimit = 100_000; + const baseHash = ci?.baseHash ?? 'HEAD~1'; + + const pullDiffResult = await spawnAsync( + 'git', + ['diff', baseHash], + { stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS } + ); + if (!pullDiffResult.code) + return pullDiffResult.stdout.substring(0, diffLimit); } diff --git a/packages/playwright/src/plugins/index.ts b/packages/playwright/src/plugins/index.ts index 2f7995cb2f..7734145468 100644 --- a/packages/playwright/src/plugins/index.ts +++ b/packages/playwright/src/plugins/index.ts @@ -35,4 +35,3 @@ export type TestRunnerPluginRegistration = { }; export { webServer } from './webServerPlugin'; -export { gitCommitInfo } from './gitCommitInfoPlugin'; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 8653327f0e..1e9fef6b6c 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1284,9 +1284,11 @@ interface TestConfig { /** * Metadata contains key-value pairs to be included in the report. For example, HTML report will display it as * key-value pairs, and JSON report will include metadata serialized as json. + * - Providing `gitCommit: 'generate'` property will populate it with the git commit details. + * - Providing `gitDiff: 'generate'` property will populate it with the git diff details. * - * Providing `'git.commit.info': {}` property will populate it with the git commit details. This is useful for CI/CD - * environments. + * On selected CI providers, both will be generated automatically. Specifying values will prevent the automatic + * generation. * * **Usage** * diff --git a/packages/trace-viewer/src/ui/errorsTab.tsx b/packages/trace-viewer/src/ui/errorsTab.tsx index 5cb28299df..a5fd9b071a 100644 --- a/packages/trace-viewer/src/ui/errorsTab.tsx +++ b/packages/trace-viewer/src/ui/errorsTab.tsx @@ -24,20 +24,20 @@ import type { StackFrame } from '@protocol/channels'; import { CopyToClipboardTextButton } from './copyToClipboard'; import { attachmentURL } from './attachmentsTab'; import { fixTestPrompt } from '@web/components/prompts'; -import type { GitCommitInfo } from '@testIsomorphic/types'; +import type { MetadataWithCommitInfo } from '@testIsomorphic/types'; import { AIConversation } from './aiConversation'; import { ToolbarButton } from '@web/components/toolbarButton'; import { useIsLLMAvailable, useLLMChat } from './llm'; import { useAsyncMemo } from '@web/uiUtils'; -const GitCommitInfoContext = React.createContext(undefined); +const CommitInfoContext = React.createContext(undefined); -export function GitCommitInfoProvider({ children, gitCommitInfo }: React.PropsWithChildren<{ gitCommitInfo: GitCommitInfo }>) { - return {children}; +export function CommitInfoProvider({ children, commitInfo }: React.PropsWithChildren<{ commitInfo: MetadataWithCommitInfo }>) { + return {children}; } -export function useGitCommitInfo() { - return React.useContext(GitCommitInfoContext); +export function useCommitInfo() { + return React.useContext(CommitInfoContext); } function usePageSnapshot(actions: modelUtil.ActionTraceEventInContext[]) { @@ -100,8 +100,7 @@ export function useErrorsTabModel(model: modelUtil.MultiTraceModel | undefined): function Error({ message, error, errorId, sdkLanguage, pageSnapshot, revealInSource }: { message: string, error: ErrorDescription, errorId: string, sdkLanguage: Language, pageSnapshot?: string, revealInSource: (error: ErrorDescription) => void }) { const [showLLM, setShowLLM] = React.useState(false); const llmAvailable = useIsLLMAvailable(); - const gitCommitInfo = useGitCommitInfo(); - const diff = gitCommitInfo?.pull_request?.diff ?? gitCommitInfo?.revision?.diff; + const metadata = useCommitInfo(); let location: string | undefined; let longLocation: string | undefined; @@ -127,8 +126,8 @@ function Error({ message, error, errorId, sdkLanguage, pageSnapshot, revealInSou
} {llmAvailable - ? - : } + ? + : }
diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 441163c5d9..4762b6057e 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -37,8 +37,9 @@ import { TestListView } from './uiModeTestListView'; import { TraceView } from './uiModeTraceView'; import { SettingsView } from './settingsView'; import { DefaultSettingsView } from './defaultSettingsView'; -import { GitCommitInfoProvider } from './errorsTab'; +import { CommitInfoProvider } from './errorsTab'; import { LLMProvider } from './llm'; +import type { MetadataWithCommitInfo } from '@testIsomorphic/types'; let xtermSize = { cols: 80, rows: 24 }; const xtermDataSource: XtermDataSource = { @@ -432,7 +433,7 @@ export const UIModeView: React.FC<{}> = ({
- + = ({ revealSource={revealSource} onOpenExternally={location => testServerConnection?.openNoReply({ location: { file: location.file, line: location.line, column: location.column } })} /> - +
} sidebar={
diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 7e9c550619..22f0064eac 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -1187,12 +1187,12 @@ for (const useIntermediateMergeReport of [true, false] as const) { ]); }); - test('should include metadata with git.commit.info', async ({ runInlineTest, writeFiles, showReport, page }) => { + test('should include metadata with gitCommit', async ({ runInlineTest, writeFiles, showReport, page }) => { const files = { 'uncommitted.txt': `uncommitted file`, 'playwright.config.ts': ` export default { - metadata: { 'git.commit.info': {}, foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] } + metadata: { foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] } }; `, 'example.spec.ts': ` @@ -1219,6 +1219,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { const result = await runInlineTest(files, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never', + GITHUB_ACTIONS: '1', GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test', GITHUB_SERVER_URL: 'https://playwright.dev', GITHUB_SHA: 'example-sha', @@ -1240,12 +1241,12 @@ for (const useIntermediateMergeReport of [true, false] as const) { `); }); - test('should include metadata with git.commit.info on GHA', async ({ runInlineTest, writeFiles, showReport, page }) => { + test('should include metadata on GHA', async ({ runInlineTest, writeFiles, showReport, page }) => { const files = { 'uncommitted.txt': `uncommitted file`, 'playwright.config.ts': ` export default { - metadata: { 'git.commit.info': {}, foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] } + metadata: { foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] } }; `, 'example.spec.ts': ` @@ -1281,6 +1282,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { const result = await runInlineTest(files, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never', + GITHUB_ACTIONS: '1', GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test', GITHUB_RUN_ID: 'example-run-id', GITHUB_SERVER_URL: 'https://playwright.dev', @@ -1295,10 +1297,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(` - list: - listitem: - - link "My PR" - - listitem: - - text: /William / - - link "Logs" + - link "https://playwright.dev/microsoft/playwright-example-for-test/commit/example-sha" - list: - listitem: "foo : value1" - listitem: "bar : {\\"prop\\":\\"value2\\"}" @@ -1306,7 +1305,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { `); }); - test('should not include git metadata w/o git.commit.info', async ({ runInlineTest, showReport, page }) => { + test('should not include git metadata w/o gitCommit', async ({ runInlineTest, showReport, page }) => { const result = await runInlineTest({ 'playwright.config.ts': ` export default {}; @@ -1330,7 +1329,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { 'playwright.config.ts': ` export default { metadata: { - 'git.commit.info': { revision: { timestamp: 'hi' } } + gitCommit: { author: { date: 'hi' } } }, }; `, @@ -2765,7 +2764,6 @@ for (const useIntermediateMergeReport of [true, false] as const) { 'playwright.config.ts': ` export default { metadata: { - 'git.commit.info': {}, foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] @@ -2799,6 +2797,7 @@ for (const useIntermediateMergeReport of [true, false] as const) { const result = await runInlineTest(files, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never', + GITHUB_ACTIONS: '1', GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test', GITHUB_RUN_ID: 'example-run-id', GITHUB_SERVER_URL: 'https://playwright.dev', diff --git a/tests/playwright-test/ui-mode-metadata.spec.ts b/tests/playwright-test/ui-mode-metadata.spec.ts index c6127cb7cb..bfbbba08a5 100644 --- a/tests/playwright-test/ui-mode-metadata.spec.ts +++ b/tests/playwright-test/ui-mode-metadata.spec.ts @@ -21,14 +21,13 @@ test('should render html report git info metadata', async ({ runUITest }) => { 'reporter.ts': ` module.exports = class Reporter { onBegin(config, suite) { - console.log('ci.link:', config.metadata['git.commit.info'].ci.link); + console.log('ci.link:', config.metadata['ci'].commitHref); } } `, 'playwright.config.ts': ` import { defineConfig } from '@playwright/test'; export default defineConfig({ - metadata: { 'git.commit.info': {} }, reporter: './reporter.ts', }); `, @@ -37,6 +36,7 @@ test('should render html report git info metadata', async ({ runUITest }) => { test('should work', async ({}) => {}); ` }, { + JENKINS_URL: '1', BUILD_URL: 'https://playwright.dev', });