diff --git a/packages/insomnia-sdk/src/objects/console.ts b/packages/insomnia-sdk/src/objects/console.ts index fb2943c04b1..2ca41908704 100644 --- a/packages/insomnia-sdk/src/objects/console.ts +++ b/packages/insomnia-sdk/src/objects/console.ts @@ -60,4 +60,9 @@ export class Console { .map(row => JSON.stringify(row) + '\n') .join('\n'); }; + + dumpLogsAsArray = () => { + return this.rows + .map(row => JSON.stringify(row) + '\n'); + }; } diff --git a/packages/insomnia-sdk/src/objects/interfaces.ts b/packages/insomnia-sdk/src/objects/interfaces.ts index ef04d108cfe..b91ba4a51be 100644 --- a/packages/insomnia-sdk/src/objects/interfaces.ts +++ b/packages/insomnia-sdk/src/objects/interfaces.ts @@ -30,5 +30,6 @@ export interface RequestContext { requestTestResults?: RequestTestResult[]; requestInfo: RequestInfoOption; execution: ExecutionOption; + logs: string[]; transientVariables?: Omit; } diff --git a/packages/insomnia-smoke-test/fixtures/runner-collection.yaml b/packages/insomnia-smoke-test/fixtures/runner-collection.yaml index 62a02aa51c9..3631c012818 100644 --- a/packages/insomnia-smoke-test/fixtures/runner-collection.yaml +++ b/packages/insomnia-smoke-test/fixtures/runner-collection.yaml @@ -373,6 +373,34 @@ resources: settingRebuildPath: true settingFollowRedirects: global _type: request + - _id: req_76bf52b201cf47269f5845795a721001 + parentId: wrk_418cad89191c418395abfeb2277fd26f + modified: 1636707449232 + created: 1636141014552 + url: http://127.0.0.1:4010/echo + name: printLogs + description: "" + method: POST + body: {} + parameters: [] + headers: [] + authentication: {} + preRequestScript: |- + console.log("it won't print"); + afterResponseScript: |- + console.log("it won't print"); + insomnia.test('happy tests', () => { + insomnia.expect(200).to.eql(200); + }); + metaSortKey: 164 + isPrivate: false + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request - _id: req_76bf52b201cf47269f5845795a711002 parentId: wrk_418cad89191c418395abfeb2277fd26f modified: 1636707449232 diff --git a/packages/insomnia-smoke-test/tests/smoke/runner.test.ts b/packages/insomnia-smoke-test/tests/smoke/runner.test.ts index b6376eba825..05d7eb20451 100644 --- a/packages/insomnia-smoke-test/tests/smoke/runner.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/runner.test.ts @@ -247,4 +247,33 @@ test.describe('runner features tests', async () => { await verifyResultRows(page, 0, 0, 4, expectedTestOrder, 1); }); + + test('settings: can turn off logs', async ({ page }) => { + + await page.getByTestId('run-collection-btn-quick').click(); + + await page.locator('.runner-request-list-printLogs').click(); + + const expectToHaveLogs = [false, true]; + + for (const expectToHaveLog of expectToHaveLogs) { + // configure + await page.getByRole('tab', { name: 'advanced' }).click(); + await page.locator('input[name="enable-log"]').click(); + + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); + + // verify there's no log + await page.getByText('1 / 1').first().click(); + await page.getByRole('tab', { name: 'Console' }).click(); + + const consoleTabContent = page.locator('.pane-two'); + if (expectToHaveLog) { + expect(consoleTabContent).toContainText("it won't print"); + } else { + expect(consoleTabContent).not.toContainText("it won't print"); + } + } + }); }); diff --git a/packages/insomnia/src/common/send-request.ts b/packages/insomnia/src/common/send-request.ts index d6ae1755f54..ef45f77edf3 100644 --- a/packages/insomnia/src/common/send-request.ts +++ b/packages/insomnia/src/common/send-request.ts @@ -18,6 +18,7 @@ import { tryToExecutePreRequestScript, tryToInterpolateRequest, } from '../network/network'; +import { defaultSendActionRuntime } from '../ui/routes/request'; import { invariant } from '../utils/invariant'; import { database } from './database'; import { generateId } from './misc'; @@ -156,6 +157,7 @@ export async function getSendRequestCallbackMemDb(environmentId: string, memDB: const postMutatedContext = await tryToExecuteAfterResponseScript({ ...requestData, ...mutatedContext, + runtime: defaultSendActionRuntime, transientVariables: mutatedContext.transientVariables || transientVariables, response, }); diff --git a/packages/insomnia/src/hidden-window.ts b/packages/insomnia/src/hidden-window.ts index 5ffdc9ffaa8..2938e77eb13 100644 --- a/packages/insomnia/src/hidden-window.ts +++ b/packages/insomnia/src/hidden-window.ts @@ -92,8 +92,6 @@ const runScript = async ( const updatedCertificates = mergeClientCertificates(context.clientCertificates, mutatedContextObject.request); const updatedCookieJar = mergeCookieJar(context.cookieJar, mutatedContextObject.cookieJar); - await window.bridge.appendFile(context.timelinePath, scriptConsole.dumpLogs()); - return { ...context, environment: { @@ -121,6 +119,7 @@ const runScript = async ( cookieJar: updatedCookieJar, globals: mutatedContextObject.globals, requestTestResults: mutatedContextObject.requestTestResults, + logs: scriptConsole.dumpLogsAsArray(), }; }; diff --git a/packages/insomnia/src/network/__tests__/network.test.ts b/packages/insomnia/src/network/__tests__/network.test.ts index d33653b6a7b..cd497eceedd 100644 --- a/packages/insomnia/src/network/__tests__/network.test.ts +++ b/packages/insomnia/src/network/__tests__/network.test.ts @@ -788,7 +788,8 @@ describe('sendCurlAndWriteTimeline()', () => { preferredHttpVersion: HttpVersions.V1_0, }, '/tmp/res_id', - 'res_id'); + 'res_id' + ); expect(JSON.parse(String(models.response.getBodyBuffer(responseV1))).options.HTTP_VERSION).toBe('V1_0'); expect(getHttpVersion(HttpVersions.V1_0).curlHttpVersion).toBe(CurlHttpVersion.V1_0); expect(getHttpVersion(HttpVersions.V1_1).curlHttpVersion).toBe(CurlHttpVersion.V1_1); diff --git a/packages/insomnia/src/network/network.ts b/packages/insomnia/src/network/network.ts index c0528b4d6a2..b3ba22fa987 100644 --- a/packages/insomnia/src/network/network.ts +++ b/packages/insomnia/src/network/network.ts @@ -33,6 +33,7 @@ import type { WebSocketRequest } from '../models/websocket-request'; import { isWorkspace, type Workspace } from '../models/workspace'; import * as pluginContexts from '../plugins/context/index'; import * as plugins from '../plugins/index'; +import { defaultSendActionRuntime, type SendActionRuntime } from '../ui/routes/request'; import { invariant } from '../utils/invariant'; import { serializeNDJSON } from '../utils/ndjson'; import { @@ -184,6 +185,7 @@ export const tryToExecutePreRequestScript = async ( userUploadEnvironment?: UserUploadEnvironment, iteration?: number, iterationCount?: number, + runtime?: SendActionRuntime, ) => { const requestGroups = ancestors.filter(doc => isRequest(doc) || isRequestGroup(doc)) as RequestGroup[]; @@ -206,6 +208,7 @@ export const tryToExecutePreRequestScript = async ( userUploadEnvironment, requestTestResults: new Array(), transientVariables, + logs: '', }; } const joinedScript = [...folderScripts].join('\n'); @@ -227,6 +230,7 @@ export const tryToExecutePreRequestScript = async ( eventName: 'prerequest', settings, transientVariables, + runtime, }); if (!mutatedContext || 'error' in mutatedContext) { return { @@ -242,6 +246,7 @@ export const tryToExecutePreRequestScript = async ( }; } await savePatchesMadeByScript(mutatedContext, environment, baseEnvironment, activeGlobalEnvironment); + return { request: mutatedContext.request, environment: mutatedContext.environment, @@ -332,6 +337,7 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe eventName, execution, transientVariables, + runtime, } = context; invariant(script, 'script must be provided'); @@ -381,6 +387,7 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe location: requestLocation, }, transientVariables, + logs: [], }, }); if ('error' in originalOutput) { @@ -423,6 +430,9 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe userUploadEnvironment.data = output?.iterationData?.data || []; userUploadEnvironment.dataPropertyOrder = userUploadEnvPropertyOrder.map; } + if (runtime) { + await runtime.appendTimeline(timelinePath, output.logs); + } if (output?.transientVariables !== undefined) { const variablesPropertyOrder = orderedJSON.parse( @@ -489,6 +499,7 @@ type RequestAndContextAndResponse = RequestContextForScript & { response: sendCurlAndWriteTimelineError | sendCurlAndWriteTimelineResponse; iteration?: number; iterationCount?: number; + runtime: SendActionRuntime; }; type RequestAndContextAndOptionalResponse = RequestContextForScript & { @@ -498,6 +509,7 @@ type RequestAndContextAndOptionalResponse = RequestContextForScript & { iteration?: number; iterationCount?: number; eventName?: RequestContext['requestInfo']['eventName']; + runtime?: SendActionRuntime; }; export async function tryToExecuteAfterResponseScript(context: RequestAndContextAndResponse) { @@ -602,6 +614,7 @@ export interface sendCurlAndWriteTimelineResponse extends ResponsePatch { timelinePath: string; statusMessage: string; cookies: Cookie[]; + timeline: string[]; } export async function sendCurlAndWriteTimeline( @@ -611,6 +624,7 @@ export async function sendCurlAndWriteTimeline( settings: Settings, timelinePath: string, responseId: string, + runtime: SendActionRuntime = defaultSendActionRuntime, ): Promise { const requestId = renderedRequest._id; const timeline: ResponseTimelineEntry[] = []; @@ -643,7 +657,9 @@ export async function sendCurlAndWriteTimeline( const output = await nodejsCurlRequest(requestOptions); if ('error' in output) { - await fs.promises.appendFile(timelinePath, serializeNDJSON(timeline)); + if (runtime) { + await runtime.appendTimeline(timelinePath, serializeNDJSON(timeline).split('\n')); + } return { _id: responseId, @@ -654,6 +670,7 @@ export async function sendCurlAndWriteTimeline( bytesRead: 0, statusMessage: output.statusMessage, timelinePath, + timeline: serializeNDJSON(timeline).split('\n'), }; } const { patch, debugTimeline, headerResults, responseBodyPath } = output; @@ -668,7 +685,9 @@ export async function sendCurlAndWriteTimeline( } const lastRedirect = headerResults[headerResults.length - 1]; - await fs.promises.appendFile(timelinePath, serializeNDJSON(timeline)); + if (runtime) { + await runtime.appendTimeline(timelinePath, serializeNDJSON(timeline).split('\n')); + } return { _id: responseId, @@ -681,6 +700,7 @@ export async function sendCurlAndWriteTimeline( statusCode: lastRedirect.code, statusMessage: lastRedirect.reason, cookies, + timeline: serializeNDJSON(timeline).split('\n'), ...patch, }; } diff --git a/packages/insomnia/src/ui/routes/request.tsx b/packages/insomnia/src/ui/routes/request.tsx index 681dcf3f78b..50c31b6ac41 100644 --- a/packages/insomnia/src/ui/routes/request.tsx +++ b/packages/insomnia/src/ui/routes/request.tsx @@ -2,6 +2,7 @@ import { createWriteStream } from 'node:fs'; import path from 'node:path'; import * as contentDisposition from 'content-disposition'; +import fs from 'fs'; import { GRAPHQL_TRANSPORT_WS_PROTOCOL, MessageType } from 'graphql-ws'; import type { RequestTestResult } from 'insomnia-sdk'; import { extension as mimeExtension } from 'mime-types'; @@ -64,6 +65,12 @@ export interface RequestLoaderData { mockServerAndRoutes: (MockServer & { routes: MockRoute[] })[]; } +export const defaultSendActionRuntime = { + appendTimeline: async (timelinePath: string, logs: string[]) => { + await fs.promises.appendFile(timelinePath, logs.join('\n')); + }, +}; + export const loader: LoaderFunction = async ({ params }): Promise => { const { organizationId, projectId, requestId, workspaceId } = params; invariant(requestId, 'Request ID is required'); @@ -407,6 +414,10 @@ export interface SendActionParams { ignoreUndefinedEnvVariable?: boolean; } +export interface SendActionRuntime { + appendTimeline: (timelinePath: string, logs: string[]) => Promise; +} + export const sendAction: ActionFunction = async ({ request, params }) => { const { requestId, workspaceId } = params; invariant(typeof requestId === 'string', 'Request ID is required'); @@ -473,29 +484,33 @@ export interface RunnerContextForRequest { responseId: string; } -export const sendActionImplementation = async ({ - requestId, - userUploadEnvironment, - shouldPromptForPathAfterResponse, - ignoreUndefinedEnvVariable, - testResultCollector, - iteration, - iterationCount, - transientVariables, -}: { - requestId: string; - shouldPromptForPathAfterResponse: boolean | undefined; - ignoreUndefinedEnvVariable: boolean | undefined; - testResultCollector?: RunnerContextForRequest; - iteration?: number; - iterationCount?: number; - userUploadEnvironment?: UserUploadEnvironment; - transientVariables?: Environment; +export const sendActionImplementation = async (options: { + requestId: string; + shouldPromptForPathAfterResponse: boolean | undefined; + ignoreUndefinedEnvVariable: boolean | undefined; + testResultCollector?: RunnerContextForRequest; + iteration?: number; + iterationCount?: number; + userUploadEnvironment?: UserUploadEnvironment; + transientVariables?: Environment; + runtime?: SendActionRuntime; }) => { + const { + requestId, + userUploadEnvironment, + shouldPromptForPathAfterResponse, + ignoreUndefinedEnvVariable, + testResultCollector, + iteration, + iterationCount, + transientVariables: nullableTransientVariables, + runtime = defaultSendActionRuntime, + } = options; + window.main.startExecution({ requestId }); const requestData = await fetchRequestData(requestId); const requestMeta = await models.requestMeta.getByParentId(requestId); - transientVariables = transientVariables || { + const transientVariables = nullableTransientVariables || { ...models.environment.init(), _id: uuidv4(), type: models.environment.type, @@ -507,7 +522,7 @@ export const sendActionImplementation = async ({ }; window.main.addExecutionStep({ requestId, stepName: 'Executing pre-request script' }); - const mutatedContext = await tryToExecutePreRequestScript(requestData, transientVariables, userUploadEnvironment, iteration, iterationCount); + const mutatedContext = await tryToExecutePreRequestScript(requestData, transientVariables, userUploadEnvironment, iteration, iterationCount, runtime); if ('error' in mutatedContext) { throw { // create response with error info, so that we can store response in db and show it in response viewer @@ -573,7 +588,8 @@ export const sendActionImplementation = async ({ requestData.caCert, mutatedContext.settings, requestData.timelinePath, - requestData.responseId + requestData.responseId, + runtime, ); window.main.completeExecutionStep({ requestId }); if ('error' in response) { @@ -598,6 +614,7 @@ export const sendActionImplementation = async ({ response, iteration, iterationCount, + runtime, }); if ('error' in postMutatedContext) { throw { diff --git a/packages/insomnia/src/ui/routes/runner.tsx b/packages/insomnia/src/ui/routes/runner.tsx index 3b148c41609..ccd3129246b 100644 --- a/packages/insomnia/src/ui/routes/runner.tsx +++ b/packages/insomnia/src/ui/routes/runner.tsx @@ -34,7 +34,7 @@ import { ResponseTimer } from '../components/response-timer'; import { getTimeAndUnit } from '../components/tags/time-tag'; import { ResponseTimelineViewer } from '../components/viewers/response-timeline-viewer'; import type { OrganizationLoaderData } from './organization'; -import { type CollectionRunnerContext, type RunnerSource, sendActionImplementation } from './request'; +import { type CollectionRunnerContext, defaultSendActionRuntime, type RunnerSource, sendActionImplementation } from './request'; import { useRootLoaderData } from './root'; import type { Child, WorkspaceLoaderData } from './workspace'; @@ -165,6 +165,7 @@ export const Runner: FC<{}> = () => { const [uploadData, setUploadData] = useState([]); const [file, setFile] = useState(null); const [bail, setBail] = useState(true); + const [keepLog, setKeepLog] = useState(true); const [isRunning, setIsRunning] = useState(false); invariant(iterationCount, 'iterationCount should not be null'); @@ -328,6 +329,7 @@ export const Runner: FC<{}> = () => { userUploadEnvs, delay, bail, + keepLog, targetFolderId: targetFolderId || '', }; submit( @@ -659,12 +661,13 @@ export const Runner: FC<{}> = () => {
@@ -894,6 +897,7 @@ export interface runCollectionActionParams { delay: number; userUploadEnvs: UserUploadEnvironment[]; bail: boolean; + keepLog: boolean; targetFolderId: string; } @@ -904,7 +908,7 @@ export const runCollectionAction: ActionFunction = async ({ request, params }) = invariant(projectId, 'Project id is required'); invariant(workspaceId, 'Workspace id is required'); - const { requests, iterationCount, delay, userUploadEnvs, bail, targetFolderId } = await request.json() as runCollectionActionParams; + const { requests, iterationCount, delay, userUploadEnvs, bail, targetFolderId, keepLog } = await request.json() as runCollectionActionParams; const source: RunnerSource = 'runner'; let testCtx: CollectionRunnerContext = { @@ -937,7 +941,14 @@ export const runCollectionAction: ActionFunction = async ({ request, params }) = }); startExecution(workspaceId); + const noLogRuntime = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + appendTimeline: async (_timelinePath: string, _logs: string[]) => { }, // no op + }; + try { + const runtime = keepLog ? defaultSendActionRuntime : noLogRuntime; + for (let i = 0; i < iterationCount; i++) { // nextRequestIdOrName is used to manual set next request in iteration from pre-request script let nextRequestIdOrName = ''; @@ -1004,6 +1015,7 @@ export const runCollectionAction: ActionFunction = async ({ request, params }) = shouldPromptForPathAfterResponse: false, ignoreUndefinedEnvVariable: true, testResultCollector: resultCollector, + runtime, transientVariables: testCtx.transientVariables, }) as RequestContext | null; if (mutatedContext?.execution?.nextRequestIdOrName) {