diff --git a/packages/cli/src/rest/__tests__/test-sessions.spec.ts b/packages/cli/src/rest/__tests__/test-sessions.spec.ts index 1d8973f26..00d9a32a7 100644 --- a/packages/cli/src/rest/__tests__/test-sessions.spec.ts +++ b/packages/cli/src/rest/__tests__/test-sessions.spec.ts @@ -3,6 +3,68 @@ import TestSessions from '../test-sessions.js' import { RequestTimeoutError } from '../errors.js' describe('TestSessions REST client', () => { + const githubRepoInfo = { + commitId: 'abc123', + github: { + reporting: true, + repository: 'checkly/playwright-reporter-demo', + sha: 'abc123', + runId: '123456', + runAttempt: '2', + workflow: 'Checkly', + job: 'validate', + eventName: 'pull_request', + ref: 'refs/pull/4/merge', + headRef: 'herve/test-checkly-action', + baseRef: 'main', + serverUrl: 'https://github.com', + }, + } + + it('passes GitHub metadata to detached test session runs', async () => { + const api = { + post: vi.fn().mockResolvedValue({ data: { testSessionId: 'session-id', sequenceIds: {} } }), + } + const testSessions = new TestSessions(api as any) + + await testSessions.run({ + name: 'Checkly', + checkRunJobs: [], + project: { logicalId: 'project' }, + runLocation: 'us-east-1', + repoInfo: githubRepoInfo, + environment: null, + shouldRecord: true, + } as any) + + expect(api.post).toHaveBeenCalledWith('/next/test-sessions/run', expect.objectContaining({ + repoInfo: githubRepoInfo, + }), expect.any(Object)) + }) + + it('passes GitHub metadata to detached trigger sessions', async () => { + const api = { + post: vi.fn().mockResolvedValue({ data: { checks: [{ id: 'check-id' }], testSessionId: 'session-id', sequenceIds: {} } }), + } + const testSessions = new TestSessions(api as any) + + await testSessions.trigger({ + name: 'Checkly', + runLocation: 'us-east-1', + shouldRecord: true, + targetTags: [['production']], + checkRunSuiteId: 'suite-id', + environmentVariables: [], + repoInfo: githubRepoInfo, + environment: null, + testRetryStrategy: null, + } as any) + + expect(api.post).toHaveBeenCalledWith('/next/test-sessions/trigger', expect.objectContaining({ + repoInfo: githubRepoInfo, + })) + }) + it('gets a public test session by ID', async () => { const api = { get: vi.fn().mockResolvedValue({ data: { testSessionId: 'session-id', results: [] } }), diff --git a/packages/cli/src/rest/test-sessions.ts b/packages/cli/src/rest/test-sessions.ts index a184e0e06..f5c5a8650 100644 --- a/packages/cli/src/rest/test-sessions.ts +++ b/packages/cli/src/rest/test-sessions.ts @@ -56,6 +56,7 @@ export type TestSessionMetadata = { commitOwner?: string commitMessage?: string branchName?: string + github?: GitInformation['github'] } export type TestSessionStatus = 'RUNNING' | 'FAILED' | 'PASSED' | 'CANCELLED' diff --git a/packages/cli/src/services/__tests__/util.spec.ts b/packages/cli/src/services/__tests__/util.spec.ts index 73d4690ec..ec2204d31 100644 --- a/packages/cli/src/services/__tests__/util.spec.ts +++ b/packages/cli/src/services/__tests__/util.spec.ts @@ -1,12 +1,51 @@ import path from 'node:path' -import { describe, it, expect } from 'vitest' +import { afterEach, beforeEach, describe, it, expect } from 'vitest' import { + getGitInformation, pathToPosix, isFileSync, } from '../util.js' +const ENV_KEYS = [ + 'CHECKLY_REPO_SHA', + 'CHECKLY_REPO_URL', + 'CHECKLY_REPO_BRANCH', + 'CHECKLY_GITHUB_REPORT', + 'CHECKLY_GITHUB_REPOSITORY', + 'CHECKLY_GITHUB_SHA', + 'CHECKLY_GITHUB_RUN_ID', + 'CHECKLY_GITHUB_RUN_ATTEMPT', + 'CHECKLY_GITHUB_WORKFLOW', + 'CHECKLY_GITHUB_JOB', + 'CHECKLY_GITHUB_EVENT_NAME', + 'CHECKLY_GITHUB_REF', + 'CHECKLY_GITHUB_HEAD_REF', + 'CHECKLY_GITHUB_BASE_REF', + 'CHECKLY_GITHUB_SERVER_URL', +] as const + describe('util', () => { + const originalEnv: Record = {} + + beforeEach(() => { + for (const key of ENV_KEYS) { + originalEnv[key] = process.env[key] + delete process.env[key] + } + }) + + afterEach(() => { + for (const key of ENV_KEYS) { + const value = originalEnv[key] + if (value === undefined) { + delete process.env[key] + } else { + process.env[key] = value + } + } + }) + describe('pathToPosix()', () => { it('should convert Windows paths', () => { expect(pathToPosix('src\\__checks__\\my_check.spec.ts', '\\')) @@ -27,4 +66,65 @@ describe('util', () => { expect(isFileSync('some random string')).toBeFalsy() }) }) + + describe('getGitInformation()', () => { + it('should not include GitHub metadata by default', () => { + process.env.CHECKLY_REPO_SHA = 'abc123' + process.env.CHECKLY_REPO_URL = 'https://github.com/checkly/demo' + process.env.CHECKLY_REPO_BRANCH = 'main' + + expect(getGitInformation()).toEqual(expect.objectContaining({ + commitId: 'abc123', + repoUrl: 'https://github.com/checkly/demo', + branchName: 'main', + })) + expect(getGitInformation()).not.toHaveProperty('github') + }) + + it('should accept common truthy values for CHECKLY_GITHUB_REPORT', () => { + process.env.CHECKLY_GITHUB_SHA = 'abc123def456' + for (const value of ['true', 'TRUE', 'True', '1', ' true ']) { + process.env.CHECKLY_GITHUB_REPORT = value + expect(getGitInformation()).toHaveProperty('github') + } + for (const value of ['false', '0', 'yes', 'on', '']) { + process.env.CHECKLY_GITHUB_REPORT = value + expect(getGitInformation()).not.toHaveProperty('github') + } + }) + + it('should include GitHub Actions metadata when Checkly GitHub reporting is enabled', () => { + process.env.CHECKLY_GITHUB_REPORT = 'true' + process.env.CHECKLY_GITHUB_REPOSITORY = 'checkly/playwright-reporter-demo' + process.env.CHECKLY_GITHUB_SHA = 'abc123def456' + process.env.CHECKLY_GITHUB_RUN_ID = '123456' + process.env.CHECKLY_GITHUB_RUN_ATTEMPT = '2' + process.env.CHECKLY_GITHUB_WORKFLOW = 'Checkly' + process.env.CHECKLY_GITHUB_JOB = 'validate' + process.env.CHECKLY_GITHUB_EVENT_NAME = 'pull_request' + process.env.CHECKLY_GITHUB_REF = 'refs/pull/4/merge' + process.env.CHECKLY_GITHUB_HEAD_REF = 'herve/test-checkly-action' + process.env.CHECKLY_GITHUB_BASE_REF = 'main' + process.env.CHECKLY_GITHUB_SERVER_URL = 'https://github.com' + + expect(getGitInformation()).toEqual(expect.objectContaining({ + commitId: 'abc123def456', + repoUrl: 'https://github.com/checkly/playwright-reporter-demo', + github: { + reporting: true, + repository: 'checkly/playwright-reporter-demo', + sha: 'abc123def456', + runId: '123456', + runAttempt: '2', + workflow: 'Checkly', + job: 'validate', + eventName: 'pull_request', + ref: 'refs/pull/4/merge', + headRef: 'herve/test-checkly-action', + baseRef: 'main', + serverUrl: 'https://github.com', + }, + })) + }) + }) }) diff --git a/packages/cli/src/services/util.ts b/packages/cli/src/services/util.ts index dd34772fb..750ebb997 100644 --- a/packages/cli/src/services/util.ts +++ b/packages/cli/src/services/util.ts @@ -15,12 +15,43 @@ export interface GitInformation { branchName?: string | null commitOwner?: string | null commitMessage?: string | null + github?: GitHubActionsInformation +} + +export interface GitHubActionsInformation { + reporting: true + repository?: string + sha?: string + runId?: string + runAttempt?: string + workflow?: string + job?: string + eventName?: string + ref?: string + headRef?: string + baseRef?: string + serverUrl?: string } export interface CiInformation { environment: string | null } +function getGitHubRepositoryUrl (): string | undefined { + const repository = process.env.CHECKLY_GITHUB_REPOSITORY + if (!repository) { + return undefined + } + + const serverUrl = process.env.CHECKLY_GITHUB_SERVER_URL ?? 'https://github.com' + return `${serverUrl.replace(/\/$/, '')}/${repository}` +} + +function isGitHubReportingEnabled (): boolean { + const value = (process.env.CHECKLY_GITHUB_REPORT ?? '').trim().toLowerCase() + return value === 'true' || value === '1' +} + export function findFilesRecursively (directory: string, ignoredPaths: Array = []) { if (!fsSync.statSync(directory, { throwIfNoEntry: false })?.isDirectory()) { return [] @@ -91,21 +122,48 @@ export function isFileSync (path: string): boolean { export function getGitInformation (repoUrl?: string): GitInformation | null { const repositoryInfo = gitRepoInfo() - if (!process.env.CHECKLY_REPO_SHA && !process.env.CHECKLY_TEST_REPO_SHA && !repositoryInfo.sha) { + if ( + !process.env.CHECKLY_REPO_SHA + && !process.env.CHECKLY_TEST_REPO_SHA + && !process.env.CHECKLY_GITHUB_SHA + && !repositoryInfo.sha + ) { return null } // safe way to remove the email address const committer = (repositoryInfo.committer?.match(/([^<]+)/) || [])[1]?.trim() - return { - commitId: process.env.CHECKLY_REPO_SHA ?? process.env.CHECKLY_TEST_REPO_SHA ?? repositoryInfo.sha, - repoUrl: process.env.CHECKLY_REPO_URL ?? process.env.CHECKLY_TEST_REPO_URL ?? repoUrl, + const gitInformation: GitInformation = { + commitId: process.env.CHECKLY_REPO_SHA + ?? process.env.CHECKLY_TEST_REPO_SHA + ?? process.env.CHECKLY_GITHUB_SHA + ?? repositoryInfo.sha, + repoUrl: process.env.CHECKLY_REPO_URL ?? process.env.CHECKLY_TEST_REPO_URL ?? repoUrl ?? getGitHubRepositoryUrl(), branchName: process.env.CHECKLY_REPO_BRANCH ?? process.env.CHECKLY_TEST_REPO_BRANCH ?? repositoryInfo.branch, commitOwner: process.env.CHECKLY_REPO_COMMIT_OWNER ?? process.env.CHECKLY_TEST_REPO_COMMIT_OWNER ?? committer, commitMessage: process.env.CHECKLY_REPO_COMMIT_MESSAGE ?? process.env.CHECKLY_TEST_REPO_COMMIT_MESSAGE ?? repositoryInfo.commitMessage, } + + if (isGitHubReportingEnabled()) { + gitInformation.github = { + reporting: true, + repository: process.env.CHECKLY_GITHUB_REPOSITORY, + sha: process.env.CHECKLY_GITHUB_SHA, + runId: process.env.CHECKLY_GITHUB_RUN_ID, + runAttempt: process.env.CHECKLY_GITHUB_RUN_ATTEMPT, + workflow: process.env.CHECKLY_GITHUB_WORKFLOW, + job: process.env.CHECKLY_GITHUB_JOB, + eventName: process.env.CHECKLY_GITHUB_EVENT_NAME, + ref: process.env.CHECKLY_GITHUB_REF, + headRef: process.env.CHECKLY_GITHUB_HEAD_REF, + baseRef: process.env.CHECKLY_GITHUB_BASE_REF, + serverUrl: process.env.CHECKLY_GITHUB_SERVER_URL, + } + } + + return gitInformation } export function getCiInformation (): CiInformation {