diff --git a/messages/shared.md b/messages/shared.md index af9268c..a9c7f45 100644 --- a/messages/shared.md +++ b/messages/shared.md @@ -1,3 +1,11 @@ # flags.result-format.summary Format of the test run results. + +# flags.output-dir.summary + +Directory to write the test results to. + +# flags.output-dir.description + +If test run is complete, write the results to the specified directory. If the tests are still running, the test results will not be written. diff --git a/src/commands/agent/test/results.ts b/src/commands/agent/test/results.ts index 5443fc4..7fb2ef5 100644 --- a/src/commands/agent/test/results.ts +++ b/src/commands/agent/test/results.ts @@ -7,8 +7,9 @@ import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; -import { AgentTester, AgentTestDetailsResponse, humanFormat } from '@salesforce/agents'; -import { resultFormatFlag } from '../../../flags.js'; +import { AgentTester, AgentTestDetailsResponse } from '@salesforce/agents'; +import { resultFormatFlag, testOutputDirFlag } from '../../../flags.js'; +import { handleTestResults } from '../../../handleTestResults.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.test.results'); @@ -30,6 +31,7 @@ export default class AgentTestResults extends SfCommand required: true, }), 'result-format': resultFormatFlag(), + 'output-dir': testOutputDirFlag(), }; public async run(): Promise { @@ -37,9 +39,13 @@ export default class AgentTestResults extends SfCommand const agentTester = new AgentTester(flags['target-org'].getConnection(flags['api-version'])); const response = await agentTester.details(flags['job-id']); - if (flags['result-format'] === 'human') { - this.log(await humanFormat(flags['job-id'], response)); - } + await handleTestResults({ + id: flags['job-id'], + format: flags['result-format'], + results: response, + jsonEnabled: this.jsonEnabled(), + outputDir: flags['output-dir'], + }); return response; } } diff --git a/src/commands/agent/test/resume.ts b/src/commands/agent/test/resume.ts index 2087612..4fbd2de 100644 --- a/src/commands/agent/test/resume.ts +++ b/src/commands/agent/test/resume.ts @@ -7,10 +7,11 @@ import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; -import { AgentTester, humanFormat } from '@salesforce/agents'; +import { AgentTester } from '@salesforce/agents'; import { AgentTestCache } from '../../../agentTestCache.js'; import { TestStages } from '../../../testStages.js'; -import { resultFormatFlag } from '../../../flags.js'; +import { resultFormatFlag, testOutputDirFlag } from '../../../flags.js'; +import { handleTestResults } from '../../../handleTestResults.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.test.resume'); @@ -48,6 +49,7 @@ export default class AgentTestResume extends SfCommand { description: messages.getMessage('flags.wait.description'), }), 'result-format': resultFormatFlag(), + 'output-dir': testOutputDirFlag(), }; public async run(): Promise { @@ -68,9 +70,13 @@ export default class AgentTestResume extends SfCommand { mso.stop(); - if (response && flags['result-format'] === 'human') { - this.log(await humanFormat(name ?? aiEvaluationId, response)); - } + await handleTestResults({ + id: aiEvaluationId, + format: flags['result-format'], + results: response, + jsonEnabled: this.jsonEnabled(), + outputDir: flags['output-dir'], + }); return { status: 'COMPLETED', diff --git a/src/commands/agent/test/run.ts b/src/commands/agent/test/run.ts index 1b2a2c9..51f9da5 100644 --- a/src/commands/agent/test/run.ts +++ b/src/commands/agent/test/run.ts @@ -7,11 +7,12 @@ import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; -import { AgentTester, humanFormat } from '@salesforce/agents'; +import { AgentTester } from '@salesforce/agents'; import { colorize } from '@oclif/core/ux'; -import { resultFormatFlag } from '../../../flags.js'; +import { resultFormatFlag, testOutputDirFlag } from '../../../flags.js'; import { AgentTestCache } from '../../../agentTestCache.js'; import { TestStages } from '../../../testStages.js'; +import { handleTestResults } from '../../../handleTestResults.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.test.run'); @@ -47,6 +48,7 @@ export default class AgentTestRun extends SfCommand { description: messages.getMessage('flags.wait.description'), }), 'result-format': resultFormatFlag(), + 'output-dir': testOutputDirFlag(), }; public async run(): Promise { @@ -69,9 +71,14 @@ export default class AgentTestRun extends SfCommand { mso.stop(); - if (detailsResponse && flags['result-format'] === 'human') { - this.log(await humanFormat(flags.name, detailsResponse)); - } + await handleTestResults({ + id: response.aiEvaluationId, + format: flags['result-format'], + results: detailsResponse, + jsonEnabled: this.jsonEnabled(), + outputDir: flags['output-dir'], + }); + return { status: 'COMPLETED', aiEvaluationId: response.aiEvaluationId, diff --git a/src/flags.ts b/src/flags.ts index 418e308..0478cbf 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -14,9 +14,15 @@ export const resultFormatFlag = Flags.option({ options: [ 'json', 'human', + 'junit', // 'tap', - // 'junit' ] as const, default: 'human', summary: messages.getMessage('flags.result-format.summary'), }); + +export const testOutputDirFlag = Flags.custom({ + char: 'f', + description: messages.getMessage('flags.output-dir.description'), + summary: messages.getMessage('flags.output-dir.summary'), +}); diff --git a/src/handleTestResults.ts b/src/handleTestResults.ts new file mode 100644 index 0000000..38b784d --- /dev/null +++ b/src/handleTestResults.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { writeFile, mkdir } from 'node:fs/promises'; +import { AgentTestDetailsResponse, jsonFormat, humanFormat, junitFormat } from '@salesforce/agents'; +import { Ux } from '@salesforce/sf-plugins-core/Ux'; + +async function writeFileToDir(outputDir: string, fileName: string, content: string): Promise { + // if directory doesn't exist, create it + await mkdir(outputDir, { recursive: true }); + + await writeFile(`${outputDir}/${fileName}`, content); +} + +export async function handleTestResults({ + id, + format, + results, + jsonEnabled, + outputDir, +}: { + id: string; + format: 'human' | 'json' | 'junit'; + results: AgentTestDetailsResponse | undefined; + jsonEnabled: boolean; + outputDir?: string; +}): Promise { + if (!results) { + // do nothing since there are no results to handle + return; + } + + const ux = new Ux({ jsonEnabled }); + + if (format === 'human') { + const formatted = await humanFormat(results); + ux.log(formatted); + if (outputDir) { + await writeFileToDir(outputDir, `test-result-${id}.txt`, formatted); + } + } + + if (format === 'json') { + const formatted = await jsonFormat(results); + ux.log(formatted); + if (outputDir) { + await writeFileToDir(outputDir, `test-result-${id}.json`, formatted); + } + } + + if (format === 'junit') { + const formatted = await junitFormat(results); + ux.log(formatted); + if (outputDir) { + await writeFileToDir(outputDir, `test-result-${id}.xml`, formatted); + } + } +}