From bf1284c8f107c337d07c8f8217284bfeefeea6c4 Mon Sep 17 00:00:00 2001 From: Andrew Goldis Date: Wed, 22 Feb 2023 15:52:57 -0800 Subject: [PATCH] configurable batch size --- .vscode/launch.json | 2 +- CHANGELOG.md | 4 +- README.md | 2 +- examples/webapp/.env.example | 2 +- examples/webapp/currents-runner.sh | 2 +- examples/webapp/currents.config.js | 1 + packages/cypress-cloud/index.ts | 80 ++++++++++++++----- .../lib/api/__tests__/api.test.ts | 2 +- packages/cypress-cloud/lib/api/api.ts | 19 ++--- .../cypress-cloud/lib/api/types/instance.ts | 14 ++-- packages/cypress-cloud/lib/api/types/run.ts | 5 +- packages/cypress-cloud/lib/cli/cli.ts | 18 +++-- packages/cypress-cloud/lib/config.ts | 21 +++-- packages/cypress-cloud/lib/cypress.ts | 4 +- packages/cypress-cloud/lib/env.ts | 3 + .../cypress-cloud/lib/httpClient/config.ts | 2 +- .../lib/httpClient/httpClient.ts | 2 +- packages/cypress-cloud/types.ts | 3 + turbo.json | 3 +- 19 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 packages/cypress-cloud/lib/env.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 71527174..eb2d1dd5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "configurations": [ { "name": "Attach", - "port": 9229, + "port": 9777, "request": "attach", "skipFiles": ["/**"], "type": "node" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9608a1f3..0c1eca2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,14 +49,14 @@ ### Bug Fixes -- add CURRENTS_API_BASE_URL env var ([9635424](https://github.com/currents-dev/cypress-cloud/commit/9635424b54dcea00c4fd0485060b7ec3581b8fc5)) +- add CURRENTS_API_URL env var ([9635424](https://github.com/currents-dev/cypress-cloud/commit/9635424b54dcea00c4fd0485060b7ec3581b8fc5)) - capture uploads stdout ([6489278](https://github.com/currents-dev/cypress-cloud/commit/648927803a6a8f43e2d6aff18fcd69c57f7be4f5)) - cypress start commands ([f79e9fd](https://github.com/currents-dev/cypress-cloud/commit/f79e9fd859f9bb8c802dbc17cd132ff5d9941eb8)) - get projectRoot from resolved cypress config ([7ada7c3](https://github.com/currents-dev/cypress-cloud/commit/7ada7c37d95ea04b185a99cc89581b87dfa7ecfa)) ### Features -- add CURRENTS_API_BASE_URL to be able to change base URL ([b6b7980](https://github.com/currents-dev/cypress-cloud/commit/b6b798068c9d4afb33979dde091ce90a992b05b2)) +- add CURRENTS_API_URL to be able to change base URL ([b6b7980](https://github.com/currents-dev/cypress-cloud/commit/b6b798068c9d4afb33979dde091ce90a992b05b2)) - add initial capture to uploaded results ([b064cfc](https://github.com/currents-dev/cypress-cloud/commit/b064cfc3fd8b46e9a46bb567c5f5439cf78a2964)), closes [#37](https://github.com/currents-dev/cypress-cloud/issues/37) [#23](https://github.com/currents-dev/cypress-cloud/issues/23) - debug and logging ([34570be](https://github.com/currents-dev/cypress-cloud/commit/34570beac3d82cdf55b4a96631b6ac9810a1eb26)) diff --git a/README.md b/README.md index 3f35f914..da974b1e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ module.exports = { Override the default configuration values via environment variables: -- `CURRENTS_API_BASE_URL` - sorry-cypress users - set the URL of your director service +- `CURRENTS_API_URL` - sorry-cypress users - set the URL of your director service - `CURRENTS_PROJECT_ID` - set the `projectId` - `CURRENTS_RECORD_KEY` - cloud service record key diff --git a/examples/webapp/.env.example b/examples/webapp/.env.example index afb05dc3..474149b5 100644 --- a/examples/webapp/.env.example +++ b/examples/webapp/.env.example @@ -1,3 +1,3 @@ CURRENTS_PROJECT_ID= CURRENTS_RECORD_KEY= -CURRENTS_API_BASE_URL= +CURRENTS_API_URL= diff --git a/examples/webapp/currents-runner.sh b/examples/webapp/currents-runner.sh index be038106..5c373280 100644 --- a/examples/webapp/currents-runner.sh +++ b/examples/webapp/currents-runner.sh @@ -1,5 +1,5 @@ #!/bin/bash -export CURRENTS_API_BASE_URL=http://localhost:1234 +export CURRENTS_API_URL=http://localhost:1234 npx cypress-cloud --parallel --record --key ${CURRENTS_RECORD_KEY} --ci-build-id $(date +%s) diff --git a/examples/webapp/currents.config.js b/examples/webapp/currents.config.js index 86a9f1b4..5ce40dd6 100644 --- a/examples/webapp/currents.config.js +++ b/examples/webapp/currents.config.js @@ -1,4 +1,5 @@ module.exports = { + batchSize: 5, // how many specs to send in one batch projectId: !!(process.env.GITHUB_ACTION || process.env.CIRCLE_BRANCH) ? "Ij0RfK" : "l4zuz8", diff --git a/packages/cypress-cloud/index.ts b/packages/cypress-cloud/index.ts index 5ff16f68..9e372b54 100644 --- a/packages/cypress-cloud/index.ts +++ b/packages/cypress-cloud/index.ts @@ -16,16 +16,23 @@ import { import { findSpecs } from "./lib/specMatcher"; import { CurrentsRunParameters, SummaryResults } from "./types"; -import { createInstances, createRun } from "./lib/api/api"; -import { CreateInstancePayload } from "./lib/api/types/instance"; +import Debug from "debug"; +import { createInstance, createMultiInstances, createRun } from "./lib/api/api"; +import { + CreateInstancePayload, + InstanceResponseSpecDetails, +} from "./lib/api/types/instance"; import { guessBrowser } from "./lib/browser"; import { getCI } from "./lib/ciProvider"; import { runSpecFileSafe } from "./lib/cypress"; +import { isCurrents } from "./lib/env"; import { getGitInfo } from "./lib/git"; import { bold, divider, error, info, spacer, title, warn } from "./lib/log"; import { getPlatformInfo } from "./lib/platform"; import { summaryTable } from "./lib/table"; +const debug = Debug("currents:index"); + /** * Run the Cypress tests and return the results. * @@ -35,8 +42,18 @@ import { summaryTable } from "./lib/table"; export async function run(params: CurrentsRunParameters) { spacer(); - const { key, projectId, group, parallel, ciBuildId, tag, testingType } = - params; + const { + key, + projectId, + group, + parallel, + ciBuildId, + tag, + testingType, + batchSize, + } = params; + + debug("run params %o", params); const config = await getConfig(params); const specPattern = params.spec || config.specPattern; @@ -88,13 +105,14 @@ export async function run(params: CurrentsRunParameters) { specPattern: [specPattern].flat(2), tags: tag, testingType, + batchSize, }); info( "Params:", `Tags: ${tag?.join(",") ?? false}; Group: ${group ?? false}; Parallel: ${ parallel ?? false - }` + }; Batch Size: ${batchSize}` ); info("🎥 Run URL:", bold(run.runUrl)); @@ -113,7 +131,6 @@ export async function run(params: CurrentsRunParameters) { params ); - console.log(results); const testResults = summarizeTestResults(Object.values(results)); divider(); @@ -145,13 +162,40 @@ async function runTillDone( const uploadTasks: Promise[] = []; let hasMore = true; - async function runSpecFile() { - const instances = await createInstances({ - runId, - groupId, - machineId, - platform, - }); + async function runSpecFiles() { + let instances = { + specs: [] as InstanceResponseSpecDetails[], + claimedInstances: 0, + totalInstances: 0, + }; + + if (isCurrents() || !!process.env.CURRENTS_BATCHED_ORCHESTRATION) { + debug("Running batched orchestration %d", cypressRunOptions.batchSize); + instances = await createMultiInstances({ + runId, + groupId, + machineId, + platform, + batchSize: cypressRunOptions.batchSize, + }); + } else { + const response = await createInstance({ + runId, + groupId, + machineId, + platform, + }); + if (response.spec !== null && response.instanceId !== null) { + instances.specs.push({ + spec: response.spec, + instanceId: response.instanceId, + }); + } + instances.claimedInstances = response.claimedInstances; + instances.totalInstances = response.totalInstances; + } + + debug; if (instances.specs.length === 0) { hasMore = false; @@ -183,8 +227,6 @@ async function runTillDone( ); } - console.dir(cypressResult); - instances.specs.forEach((spec) => { summary[spec.spec] = { ...cypressResult, @@ -213,15 +255,9 @@ async function runTillDone( resetCapture(); } - // bus.on("after", () => console.log("🔥 AFTER")); - // bus.on("after", async () => await runSpecFile()); - while (hasMore) { - await runSpecFile(); - // await new Promise((resolve) => setTimeout(resolve, 1000)); - // uploadTasks.push(runSpecFile()); + await runSpecFiles(); } - // waitUntil(() => !hasMore, 0, 1000); await Promise.allSettled(uploadTasks); return summary; diff --git a/packages/cypress-cloud/lib/api/__tests__/api.test.ts b/packages/cypress-cloud/lib/api/__tests__/api.test.ts index 440bc066..a7ca6cc5 100644 --- a/packages/cypress-cloud/lib/api/__tests__/api.test.ts +++ b/packages/cypress-cloud/lib/api/__tests__/api.test.ts @@ -20,7 +20,7 @@ const API_BASEURL = "http://localhost:1234"; describe("cloud/api", () => { beforeAll(() => { - process.env.CURRENTS_API_BASE_URL = API_BASEURL; + process.env.CURRENTS_API_URL = API_BASEURL; }); describe("createRun", () => { diff --git a/packages/cypress-cloud/lib/api/api.ts b/packages/cypress-cloud/lib/api/api.ts index f1a23ff3..55f1592b 100644 --- a/packages/cypress-cloud/lib/api/api.ts +++ b/packages/cypress-cloud/lib/api/api.ts @@ -1,5 +1,6 @@ import { makeRequest } from "../httpClient"; import { + CreateInstanceCyPayload, CreateInstancePayload, CreateInstanceResponse, CreateInstancesResponse, @@ -49,24 +50,14 @@ export const createInstance = async ({ return respone.data; }; -export const createInstances = async ({ - runId, - groupId, - machineId, - platform, -}: CreateInstancePayload) => { +export const createMultiInstances = async (data: CreateInstanceCyPayload) => { const respone = await makeRequest< CreateInstancesResponse, - CreateInstancePayload + CreateInstanceCyPayload >({ method: "POST", - url: `runs/${runId}/cy/instances`, - data: { - runId, - groupId, - machineId, - platform, - }, + url: `runs/${data.runId}/cy/instances`, + data, }); return respone.data; diff --git a/packages/cypress-cloud/lib/api/types/instance.ts b/packages/cypress-cloud/lib/api/types/instance.ts index e4ad141e..2b0a772b 100644 --- a/packages/cypress-cloud/lib/api/types/instance.ts +++ b/packages/cypress-cloud/lib/api/types/instance.ts @@ -84,18 +84,22 @@ export type CreateInstancePayload = { platform: Platform; }; +export type CreateInstanceCyPayload = CreateInstancePayload & { + batchSize: number; +}; export type CreateInstanceResponse = { spec: string | null; instanceId: string | null; claimedInstances: number; totalInstances: number; }; -export type CreateInstancesResponse = { - specs: Array<{ - spec: string; - instanceId: string; - }>; +export type InstanceResponseSpecDetails = { + spec: string; + instanceId: string; +}; +export type CreateInstancesResponse = { + specs: Array; claimedInstances: number; totalInstances: number; }; diff --git a/packages/cypress-cloud/lib/api/types/run.ts b/packages/cypress-cloud/lib/api/types/run.ts index ba38271d..3a2bfff1 100644 --- a/packages/cypress-cloud/lib/api/types/run.ts +++ b/packages/cypress-cloud/lib/api/types/run.ts @@ -1,5 +1,5 @@ -import { CiParams, CiProvider } from "cypress-cloud/lib/ciProvider"; -import { Platform } from "cypress-cloud/types"; +import { CiParams, CiProvider } from "@currents/cypress/lib/ciProvider"; +import { Platform } from "@currents/cypress/types"; export type CreateRunPayload = { ci: { @@ -20,6 +20,7 @@ export type CreateRunPayload = { tags?: string[]; testingType: "e2e" | "component"; timeout?: number; + batchSize: number; }; export type CloudWarning = { diff --git a/packages/cypress-cloud/lib/cli/cli.ts b/packages/cypress-cloud/lib/cli/cli.ts index e3929acd..d243b71a 100644 --- a/packages/cypress-cloud/lib/cli/cli.ts +++ b/packages/cypress-cloud/lib/cli/cli.ts @@ -4,6 +4,7 @@ import { isObject, omit, pickBy } from "lodash"; import { CurrentsRunParameters, StrippedCypressModuleAPIOptions, + TestingType, } from "../../types"; import { getCurrentsConfig } from "../config"; import { sanitizeAndConvertNestedArgs } from "./parser"; @@ -100,7 +101,7 @@ export function parseOptions( ...args: Parameters ) { _program.parse(...args); - debug("parsed CLI options", _program.opts()); + debug("parsed CLI flags %o", _program.opts()); const { e2e, component } = _program.opts(); if (e2e && component) { @@ -126,6 +127,7 @@ export function getStrippedCypressOptions( ): StrippedCypressModuleAPIOptions { return pickBy( omit(params, [ + "batchSize", "projectId", "record", "key", @@ -176,7 +178,7 @@ const dashed = (v: string) => v.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()); async function getRunParameters( cliOptions: ReturnType ): Promise { - const { projectId, recordKey } = await getCurrentsConfig(); + const { projectId, recordKey, batchSize } = await getCurrentsConfig(); const key = cliOptions.key ?? process.env.CURRENTS_RECORD_KEY ?? recordKey; if (!key) { @@ -193,10 +195,8 @@ async function getRunParameters( ); } - const result = omit({ ...cliOptions }, "e2e", "component", "tag"); - - return { - ...result, + const result = { + ...omit({ ...cliOptions }, "e2e", "component", "tag"), config: sanitizeAndConvertNestedArgs(cliOptions.config, "config"), env: sanitizeAndConvertNestedArgs(cliOptions.env, "env"), reporterOptions: sanitizeAndConvertNestedArgs( @@ -204,8 +204,12 @@ async function getRunParameters( "reporterOptions" ), tag: cliOptions.tag, - testingType: cliOptions.component ? "component" : "e2e", + testingType: cliOptions.component ? "component" : ("e2e" as TestingType), key, projectId: _projectId, + batchSize, }; + + debug("parsed run params: %o", result); + return result; } diff --git a/packages/cypress-cloud/lib/config.ts b/packages/cypress-cloud/lib/config.ts index 0617c2d9..cc0ef635 100644 --- a/packages/cypress-cloud/lib/config.ts +++ b/packages/cypress-cloud/lib/config.ts @@ -2,21 +2,32 @@ import Debug from "debug"; import path from "path"; import { CurrentsRunParameters, DetectedBrowser } from "../types"; import { bootCypress } from "./bootstrap"; +import { warn } from "./log"; import { getRandomPort } from "./utils"; const debug = Debug("currents:config"); -export type CurrentsConfig = { projectId?: string; recordKey?: string }; +export type CurrentsConfig = { + projectId?: string; + recordKey?: string; + batchSize: number; +}; export async function getCurrentsConfig(): Promise { const configFilePath = await getConfigFilePath(); debug("loading currents config file from '%s'", configFilePath); - let config: CurrentsConfig = {}; + const defaultConfig: CurrentsConfig = { + batchSize: 3, + }; try { - config = require(configFilePath); - return config; + const fsConfig = require(configFilePath); + return { + ...defaultConfig, + ...fsConfig, + }; } catch (e) { - return {}; + warn("failed to load currents config file: %s", configFilePath); + return defaultConfig; } } diff --git a/packages/cypress-cloud/lib/cypress.ts b/packages/cypress-cloud/lib/cypress.ts index 9331698e..693f911d 100644 --- a/packages/cypress-cloud/lib/cypress.ts +++ b/packages/cypress-cloud/lib/cypress.ts @@ -29,10 +29,10 @@ export async function runSpecFile( }, spec, }; - debug("%s: running cypress with options %o", spec, options); + debug("running cypress with options %o", options); const result = await cypress.run(options); - debug("%s: cypress run result %o", spec, result); + debug("cypress run result %o", result); return result; } diff --git a/packages/cypress-cloud/lib/env.ts b/packages/cypress-cloud/lib/env.ts new file mode 100644 index 00000000..5fe3f208 --- /dev/null +++ b/packages/cypress-cloud/lib/env.ts @@ -0,0 +1,3 @@ +import { getBaseUrl } from "./httpClient/config"; + +export const isCurrents = () => getBaseUrl() === "https://cy.currents.dev"; diff --git a/packages/cypress-cloud/lib/httpClient/config.ts b/packages/cypress-cloud/lib/httpClient/config.ts index b15fa588..f5f871dc 100644 --- a/packages/cypress-cloud/lib/httpClient/config.ts +++ b/packages/cypress-cloud/lib/httpClient/config.ts @@ -17,4 +17,4 @@ export const isRetriableError = (err: AxiosError | Error) => { export const getDelays = () => [30 * 1000, 60 * 1000, 2 * 60 * 1000]; // 30s, 1min, 2min export const getBaseUrl = () => - process.env.CURRENTS_API_BASE_URL || "https://cy.currents.dev"; + process.env.CURRENTS_API_URL || "https://cy.currents.dev"; diff --git a/packages/cypress-cloud/lib/httpClient/httpClient.ts b/packages/cypress-cloud/lib/httpClient/httpClient.ts index 88f4b474..1261a5ba 100644 --- a/packages/cypress-cloud/lib/httpClient/httpClient.ts +++ b/packages/cypress-cloud/lib/httpClient/httpClient.ts @@ -41,7 +41,7 @@ export const makeRequest = ( "x-cypress-request-attempt": retryIndex, "x-cypress-run-id": _runId, "x-cypress-version": _cypressVersion, - "x-currents-version": _currentsVersion, + "x-ccy-version": _currentsVersion ?? "0.0.0", ...config.headers, }, }; diff --git a/packages/cypress-cloud/types.ts b/packages/cypress-cloud/types.ts index 1c43dd94..3b7b03cb 100644 --- a/packages/cypress-cloud/types.ts +++ b/packages/cypress-cloud/types.ts @@ -135,4 +135,7 @@ export type CurrentsRunParameters = StrippedCypressModuleAPIOptions & { /** The project ID to use. If not specified, will use the projectId from currents.config.js or process.env.CURRENTS_PROJECT_ID */ projectId: string; + + /** The batch size defines how many spec files will be served in one orchestration "batch". If not specified, will use the projectId from currents.config.js, the default value is 3 */ + batchSize: number; }; diff --git a/turbo.json b/turbo.json index 02645490..5fdcc0a7 100644 --- a/turbo.json +++ b/turbo.json @@ -12,8 +12,9 @@ "outputs": ["dist/**", ".next/**"], "env": [ "DEBUG", + "CURRENTS_BATCHED_ORCHESTRATION", "CURRENTS_PROJECT_ID", - "CURRENTS_API_BASE_URL", + "CURRENTS_API_URL", "CURRENTS_RECORD_KEY", "TF_BUILD", "TF_BUILD_BUILDNUMBER",