refactor: begin to use axios for cloud api requests (#31041)

* patch axios for v8 snapshots

* export httpsAgent and httpAgent discretely

* set up axios interceptors for logging and error response transformation

* use unified error transform fn

* create instance api reqs, timeouts, tests

* move axios middleware to its own dir & refactor

* refactor error handling, enable retries in createInstance

* fix invocation of createInstance - not caught by ts because record.js is js

* retry on 500 - according to system test, this is expected behavior

* resolve snapshots, report retries to stdout

* fix cdp connection usage of shouldRetry due to newly unknown error type

* axios doesnt fully follow RequestOptions shape when adding request to https agent

* note why uri is treated as optional

* hail mary on getting axios to work with v8 snapshots

* update lockfile, force no-rewrite on more axios files

* attempt to fix v8 snapshots

* add verbose debugging to api request logging

* enable verbose api debugging on server unit tests

* fix nock pattern for createInstance

* remove request logging unit test - sinon/mocha does not assert correctly

* fix a few unit tests

* use runAllAsync rather than waiting an arbitrary time for sinon fake timer

* move create_instance spec to ts file, remove redundant test

* rm debug on ci

* clarify comment on change inpackages/network

* correct .uri property on patched RequestOptions to be optional

* rm unused log_requests.ts, DRY error messages

* resolve types with record.ts migration

---------

Co-authored-by: Ryan Manuel <ryanm@cypress.io>
This commit is contained in:
Cacie Prins
2025-03-10 10:58:09 -04:00
committed by GitHub
parent 996da962b4
commit bbd7efc0dd
26 changed files with 840 additions and 253 deletions
+1
View File
@@ -20,6 +20,7 @@
},
"dependencies": {
"ansi_up": "5.0.0",
"axios": "^1.7.9",
"chalk": "^2.4.2",
"lodash": "^4.17.21",
"pluralize": "8.0.0",
+16 -5
View File
@@ -10,6 +10,7 @@ import { humanTime, logError, parseResolvedPattern, pluralize } from './errorUti
import { errPartial, errTemplate, fmt, theme, PartialErr } from './errTemplate'
import { stackWithoutMessage } from './stackUtils'
import type { ClonedError, ConfigValidationFailureInfo, CypressError, ErrTemplateResult, ErrorLike } from './errorTypes'
import { normalizeNetworkErrorMessage } from './normalizeNetworkErrorMessage'
const ansi_up = new AU()
@@ -155,14 +156,18 @@ export const AllCypressErrors = {
CLOUD_CANCEL_SKIPPED_SPEC: () => {
return errTemplate`${fmt.off(`\n `)}This spec and its tests were skipped because the run has been canceled.`
},
CLOUD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delayMs: number, response: Error}) => {
CLOUD_API_RESPONSE_FAILED_RETRYING: (
arg1: {tries: number, delayMs: number, response: Error },
) => {
const time = pluralize('time', arg1.tries)
const delay = humanTime.long(arg1.delayMs, false)
const message = normalizeNetworkErrorMessage(arg1.response)
return errTemplate`\
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
${fmt.highlightSecondary(message)}
We will retry ${fmt.off(arg1.tries)} more ${fmt.off(time)} in ${fmt.off(delay)}...
`
@@ -170,10 +175,12 @@ export const AllCypressErrors = {
/* eslint-disable indent */
},
CLOUD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: Error}) => {
const message = normalizeNetworkErrorMessage(arg1.response)
return errTemplate`\
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
${fmt.highlightSecondary(message)}
Because you passed the ${fmt.flag(`--parallel`)} flag, this run cannot proceed since it requires a valid response from our servers.
@@ -183,10 +190,12 @@ export const AllCypressErrors = {
})}`
},
CLOUD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: Error}) => {
const message = normalizeNetworkErrorMessage(arg1.response)
return errTemplate`\
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
${fmt.highlightSecondary(message)}
Because you passed the ${fmt.flag(`--record`)} flag, this run cannot proceed since it requires a valid response from our servers.
@@ -196,10 +205,12 @@ export const AllCypressErrors = {
})}`
},
CLOUD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: Error}) => {
const message = normalizeNetworkErrorMessage(arg1.response)
return errTemplate`\
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
${fmt.highlightSecondary(message)}
There is likely something wrong with the request.
@@ -0,0 +1,5 @@
const AXIOS_ERROR_NAME = 'AxiosError'
export const normalizeNetworkErrorMessage = (error: Error): string => {
return error.name === AXIOS_ERROR_NAME ? error.message : `${error.name}: ${error.message}`
}
+14 -8
View File
@@ -223,9 +223,7 @@ export class CombinedAgent {
}) + options.path
}
if (!options.uri) {
options.uri = url.parse(options.href)
}
const uri = options.uri = options.uri ?? url.parse(options.href)
debug('addRequest called %o', { isHttps, ..._.pick(options, 'href') })
@@ -235,7 +233,7 @@ export class CombinedAgent {
debug('got family %o', _.pick(options, 'family', 'href'))
if (isHttps) {
_.assign(options, clientCertificateStore.getClientCertificateAgentOptionsForUrl(options.uri))
_.assign(options, clientCertificateStore.getClientCertificateAgentOptionsForUrl(uri))
return this.httpsAgent.addRequest(req, options as https.RequestOptions)
}
@@ -330,8 +328,12 @@ class HttpsAgent extends https.Agent {
// (https://github.com/nodejs/node/blob/master/lib/_http_client.js#L164) since we are a combined agent
// rather than an http or https agent. This will cause issues with fetch requests (@cypress/request already handles it:
// https://github.com/cypress-io/request/blob/master/request.js#L301-L303)
if (!options.uri.port && options.uri.protocol === 'https:') {
options.uri.port = String(443)
if (!options?.uri?.port && options?.uri?.protocol === 'https:') {
options.uri = {
...options.uri,
port: String(443),
}
options.port = 443
}
@@ -365,8 +367,8 @@ class HttpsAgent extends https.Agent {
debug(`Creating proxied socket for ${options.href} through ${options.proxy}`)
const proxy = url.parse(options.proxy)
const port = options.uri.port || '443'
const hostname = options.uri.hostname || 'localhost'
const port = options.uri?.port || '443'
const hostname = options.uri?.hostname || 'localhost'
createProxySock({ proxy, shouldRetry: options.shouldRetry }, (originalErr?, proxySocket?, triggerRetry?) => {
if (originalErr) {
@@ -441,3 +443,7 @@ class HttpsAgent extends https.Agent {
const agent = new CombinedAgent()
export default agent
export const httpsAgent = new HttpsAgent()
export const httpAgent = new HttpAgent()
@@ -193,7 +193,7 @@ export class CDPConnection {
return 100
},
shouldRetry (err) {
return !(err && CDPTerminatedError.isCDPTerminatedError(err))
return !(err && err instanceof Error && CDPTerminatedError.isCDPTerminatedError(err))
},
})()
@@ -0,0 +1,26 @@
import type { InternalAxiosRequestConfig, AxiosResponse, AxiosError, AxiosInstance } from 'axios'
import Debug from 'debug'
const debug = Debug('cypress:server:cloud:api')
const logRequest = (req: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
debug(`${req.method} ${req.url}`)
return req
}
const logResponse = (res: AxiosResponse): AxiosResponse => {
debug(`${res.config.method} ${res.config.url} Success: %d %s -> \n Response: %o`, res.status, res.statusText, res.data)
return res
}
const logResponseErr = (err: AxiosError): never => {
debug(`${err.config?.method} ${err.config?.url} Error: %s -> \n Response: %o`, err.response?.statusText || err.code, err.response?.data)
throw err
}
export const installLogging = (axios: AxiosInstance) => {
axios.interceptors.request.use(logRequest)
axios.interceptors.response.use(logResponse, logResponseErr)
}
@@ -0,0 +1,28 @@
import { isObject } from 'lodash'
import axios, { AxiosError, AxiosInstance } from 'axios'
declare module 'axios' {
export interface AxiosError {
isApiError?: boolean
}
}
export const transformError = (err: AxiosError | Error & { error?: any, statusCode: number, isApiError?: boolean }): never => {
const { data, status } = axios.isAxiosError(err) ?
{ data: err.response?.data, status: err.status } :
{ data: err.error, status: err.statusCode }
if (isObject(data)) {
const body = JSON.stringify(data, null, 2)
err.message = [status, body].join('\n\n')
}
err.isApiError = true
throw err
}
export const installErrorTransform = (axios: AxiosInstance) => {
axios.interceptors.response.use(undefined, transformError)
}
@@ -0,0 +1,50 @@
import os from 'os'
import axios, { AxiosInstance } from 'axios'
import pkg from '@packages/root'
import { httpAgent, httpsAgent } from '@packages/network/lib/agent'
import app_config from '../../../config/app.json'
import { installErrorTransform } from './axios_middleware/transform_error'
import { installLogging } from './axios_middleware/logging'
// initialized with an export for testing purposes
export const _create = (): AxiosInstance => {
const cfgKey = process.env.CYPRESS_CONFIG_ENV || process.env.CYPRESS_INTERNAL_ENV || 'development'
const instance = axios.create({
baseURL: app_config[cfgKey].api_url,
httpAgent,
httpsAgent,
headers: {
'x-os-name': os.platform(),
'x-cypress-version': pkg.version,
'User-Agent': `cypress/${pkg.version}`,
},
})
installLogging(instance)
installErrorTransform(instance)
return instance
}
export const CloudRequest = _create()
export const isRetryableCloudError = (error: unknown) => {
// setting this env via mocha's beforeEach coerces this to a string, even if it's a boolean
const disabled = process.env.DISABLE_API_RETRIES && process.env.DISABLE_API_RETRIES !== 'false'
if (disabled) {
return false
}
const axiosErr = axios.isAxiosError(error) ? error : undefined
if (axiosErr && axiosErr.status) {
return [408, 429, 500, 502, 503, 504].includes(axiosErr.status)
}
return true
}
@@ -0,0 +1,68 @@
import { CloudRequest, isRetryableCloudError } from './cloud_request'
import { asyncRetry, exponentialBackoff } from '../../util/async_retry'
import * as errors from '../../errors'
import { isAxiosError } from 'axios'
const MAX_RETRIES = 3
interface CreateInstanceResponse {
spec: string | null
instanceId: string | null
claimedInstances: number
estimatedWallClockDuration: number | null
totalInstances: number
}
interface CreateInstanceRequestBody {
spec: string | null
groupId: string
machineId: string
platform: {
browserName: string
browserVersion: string
osCpus: any[]
osMemory: Record<string, any> | null
osName: string
osVersion: string
}
}
export const createInstance = async (runId: string, instanceData: CreateInstanceRequestBody, timeout: number = 0): Promise<CreateInstanceResponse> => {
let attemptNumber = 0
return asyncRetry(async () => {
try {
const { data } = await CloudRequest.post<CreateInstanceResponse>(
`/runs/${runId}/instances`,
instanceData,
{
headers: {
'x-route-version': '5',
'x-cypress-run-id': runId,
'x-cypress-request-attempt': `${attemptNumber}`,
},
timeout,
},
)
return data
} catch (err: unknown) {
attemptNumber++
throw err
}
}, {
maxAttempts: MAX_RETRIES,
retryDelay: exponentialBackoff(),
shouldRetry: isRetryableCloudError,
onRetry: (delay, err) => {
errors.warning(
'CLOUD_API_RESPONSE_FAILED_RETRYING', {
delayMs: delay,
tries: MAX_RETRIES - attemptNumber,
response: isAxiosError(err) ? err : err instanceof Error ? err : new Error(String(err)),
},
)
},
})()
}
+10 -46
View File
@@ -25,6 +25,9 @@ import type { ProjectBase } from '../../project-base'
import type { AfterSpecDurations } from '@packages/types'
import { PUBLIC_KEY_VERSION } from '../constants'
import { createInstance } from './create_instance'
import { transformError } from './axios_middleware/transform_error'
const THIRTY_SECONDS = humanInterval('30 seconds')
const SIXTY_SECONDS = humanInterval('60 seconds')
const TWO_MINUTES = humanInterval('2 minutes')
@@ -207,19 +210,6 @@ const retryWithBackoff = (fn) => {
return attempt(0)
}
const formatResponseBody = function (err) {
// if the body is JSON object
if (_.isObject(err.error)) {
// transform the error message to include the
// stringified body (represented as the 'error' property)
const body = JSON.stringify(err.error, null, 2)
err.message = [err.statusCode, body].join('\n\n')
}
throw err
}
const tagError = function (err) {
err.isApiError = true
throw err
@@ -354,7 +344,7 @@ export default {
.catch(tagError)
},
createRun (options: CreateRunOptions) {
createRun (options: CreateRunOptions): Bluebird<CreateRunResponse> {
const preflightOptions = _.pick(options, ['projectId', 'projectRoot', 'ciBuildId', 'browser', 'testingType', 'parallel', 'timeout'])
return this.sendPreflight(preflightOptions)
@@ -448,37 +438,11 @@ export default {
return result
})
.catch(RequestErrors.StatusCodeError, formatResponseBody)
.catch(RequestErrors.StatusCodeError, transformError)
.catch(tagError)
},
createInstance (options) {
const { runId, timeout } = options
const body = _.pick(options, [
'spec',
'groupId',
'machineId',
'platform',
])
return retryWithBackoff((attemptIndex) => {
return rp.post({
body,
url: recordRoutes.instances(runId),
json: true,
encrypt: preflightResult.encrypt,
timeout: timeout ?? SIXTY_SECONDS,
headers: {
'x-route-version': '5',
'x-cypress-run-id': runId,
'x-cypress-request-attempt': attemptIndex,
},
})
})
.catch(RequestErrors.StatusCodeError, formatResponseBody)
.catch(tagError)
},
createInstance,
postInstanceTests (options) {
const { instanceId, runId, timeout, ...body } = options
@@ -496,7 +460,7 @@ export default {
},
body,
})
.catch(RequestErrors.StatusCodeError, formatResponseBody)
.catch(RequestErrors.StatusCodeError, transformError)
.catch(tagError)
})
},
@@ -516,7 +480,7 @@ export default {
},
})
.catch(RequestErrors.StatusCodeError, formatResponseBody)
.catch(RequestErrors.StatusCodeError, transformError)
.catch(tagError)
})
},
@@ -536,7 +500,7 @@ export default {
'x-cypress-request-attempt': attemptIndex,
},
})
.catch(RequestErrors.StatusCodeError, formatResponseBody)
.catch(RequestErrors.StatusCodeError, transformError)
.catch(tagError)
})
},
@@ -563,7 +527,7 @@ export default {
'metadata',
]),
})
.catch(RequestErrors.StatusCodeError, formatResponseBody)
.catch(RequestErrors.StatusCodeError, transformError)
.catch(tagError)
})
},
@@ -4,14 +4,19 @@ import Debug from 'debug'
const debug = Debug('cypress-verbose:server:is-retryable-error')
export const isRetryableError = (error?: Error) => {
export const isRetryableError = (error: unknown) => {
debug('is retryable error? system error: %s, httperror: %s, status: %d',
error && SystemError.isSystemError(error),
error && HttpError.isHttpError(error),
error && SystemError.isSystemError(error as any),
error && HttpError.isHttpError(error as any),
(error as HttpError)?.status)
return error ? (
SystemError.isSystemError(error) ||
HttpError.isHttpError(error) && [408, 429, 502, 503, 504].includes(error.status)
) : false
if (SystemError.isSystemError(error as any)) {
return true
}
if (HttpError.isHttpError(error as any)) {
return [408, 429, 502, 503, 504].includes((error as HttpError).status)
}
return false
}
+41 -29
View File
@@ -24,17 +24,6 @@ import type { Cfg } from '../project-base'
import type { RunResult } from './results'
import type { ReadyOptions } from './run'
interface InstanceOptions {
spec?: string
runId?: string
group?: string
groupId?: string
parallel?: boolean
machineId?: string
ciBuildId?: string
platform?: any
}
const debug = Debug('cypress:server:record')
const debugCiInfo = Debug('cypress:server:record:ci-info')
@@ -63,7 +52,7 @@ const warnIfProjectIdButNoRecordOption = (projectId: Cfg['projectId'], options:
return undefined
}
const throwCloudCannotProceed = ({ parallel, ciBuildId, group, err }) => {
function cloudCannotProceedErr ({ parallel, ciBuildId, group, err }): ReturnType<typeof getErrors> {
const errMsg = parallel ? 'CLOUD_CANNOT_PROCEED_IN_PARALLEL' : 'CLOUD_CANNOT_PROCEED_IN_SERIAL'
const errToThrow = getErrors(errMsg, {
@@ -77,7 +66,11 @@ const throwCloudCannotProceed = ({ parallel, ciBuildId, group, err }) => {
// tells error handler to exit immediately without running anymore specs
errToThrow.isFatalApiErr = true
throw errToThrow
return errToThrow
}
const throwCloudCannotProceed = (...args: Parameters<typeof cloudCannotProceedErr>) => {
throw cloudCannotProceedErr(...args)
}
const throwIfIndeterminateCiBuildId = (ciBuildId: ReadyOptions['ciBuildId'], parallel: ReadyOptions['parallel'], group: ReadyOptions['group']) => {
@@ -513,30 +506,50 @@ const createRun = Promise.method((options: any = {}) => {
})
})
const createInstance = (options: InstanceOptions = {}) => {
let { runId, group, groupId, parallel, machineId, ciBuildId, platform, spec } = options
interface InstanceOptions {
spec?: null | any
runId: string
group: any
groupId: string
platform: {
osCpus: any
osName: any
osMemory: any
osVersion: any
browserName: any
browserVersion: any
}
parallel?: any
ciBuildId?: any
machineId: string
}
spec = spec ? getSpecRelativePath(spec) : null
async function createInstance (options: InstanceOptions) {
let { spec, runId, group, groupId, parallel, machineId, ciBuildId, platform } = options
const resolvedSpec = spec ? getSpecRelativePath(spec) : null
try {
return await api.createInstance(runId, {
spec: resolvedSpec,
groupId,
platform,
machineId,
})
} catch (thrown: unknown) {
const err = thrown instanceof Error ? thrown : new Error(thrown as any)
return api.createInstance({
spec,
runId,
groupId,
platform,
machineId,
})
.catch((err: any) => {
debug('failed creating instance %o', {
stack: err.stack,
})
throwCloudCannotProceed({
throw cloudCannotProceedErr({
err,
group,
ciBuildId,
parallel,
})
})
}
}
const _postInstanceTests = ({
@@ -618,7 +631,7 @@ const createRunAndRecordSpecs = (options: any = {}) => {
autoCancelAfterFailures,
project,
})
.then((resp: any) => {
.then((resp) => {
telemetry.getSpan('record:createRun')?.end()
if (!resp) {
// if a forked run, can't record and can't be parallel
@@ -634,7 +647,7 @@ const createRunAndRecordSpecs = (options: any = {}) => {
let captured = null
let instanceId = null
const beforeSpecRun = (spec: any) => {
const beforeSpecRun = () => {
telemetry.startSpan({ name: 'record:beforeSpecRun' })
project.setOnTestsReceived(onTestsReceived)
capture.restore()
@@ -642,7 +655,6 @@ const createRunAndRecordSpecs = (options: any = {}) => {
captured = capture.stdout()
return createInstance({
spec,
runId,
group,
groupId,
+18 -1
View File
@@ -1,7 +1,8 @@
type RetryOptions = {
maxAttempts: number
retryDelay?: (attempt: number) => number
shouldRetry?: (err?: Error) => boolean
shouldRetry?: (err?: unknown) => boolean
onRetry?: (delay: number, err: unknown) => void
}
export function asyncRetry <
@@ -31,6 +32,10 @@ export function asyncRetry <
const delay = options.retryDelay ? options.retryDelay(attempt) : undefined
if (options.onRetry) {
options.onRetry(delay ?? 0, e)
}
if (delay !== undefined) {
await new Promise((resolve) => {
return setTimeout(resolve, delay)
@@ -52,3 +57,15 @@ export const linearDelay = (inc: number) => {
return attempt * inc
}
}
export const exponentialBackoff = ({ factor, fuzz } = {
factor: 100,
fuzz: 0.1,
}) => {
return (attempt: number) => {
const exponentialComponent = 2 ** attempt * factor
const fuzzComponent = exponentialComponent * fuzz * Math.random()
return exponentialComponent + fuzzComponent
}
}
+2
View File
@@ -38,6 +38,7 @@
"@types/mime": "^3.0.1",
"ansi_up": "5.0.0",
"ast-types": "0.13.3",
"axios": "^1.7.9",
"base64url": "^3.0.1",
"better-sqlite3": "11.5.0",
"black-hole-stream": "0.0.1",
@@ -223,6 +224,7 @@
"productName": "Cypress",
"workspaces": {
"nohoist": [
"axios",
"devtools-protocol",
"edgedriver",
"geckodriver",
+18
View File
@@ -0,0 +1,18 @@
diff --git a/node_modules/axios/lib/platform/common/utils.js b/node_modules/axios/lib/platform/common/utils.js
index 52a3186..08b92cc 100644
--- a/node_modules/axios/lib/platform/common/utils.js
+++ b/node_modules/axios/lib/platform/common/utils.js
@@ -1,6 +1,11 @@
-const hasBrowserEnv = typeof window !== 'undefined' && typeof document !== 'undefined';
+/**
+ * patched due to how v8 snapshots work - axios was incorrectly determining that
+ * it was running in a browser context, leading to runtime errors.
+ */
+
+const hasBrowserEnv = false;
-const _navigator = typeof navigator === 'object' && navigator || undefined;
+const _navigator = undefined;
/**
* Determine if we're running in a standard browser environment
+27 -139
View File
@@ -36,6 +36,8 @@ const makeError = (details = {}) => {
return _.extend(new Error(details.message || 'Some error'), details)
}
const OS_PLATFORM = 'linux'
const encryptRequest = encryption.encryptRequest
const decryptReqBodyAndRespond = ({ reqBody, resBody }, fn) => {
@@ -91,7 +93,7 @@ const decryptReqBodyAndRespond = ({ reqBody, resBody }, fn) => {
const preflightNock = (baseUrl) => {
return nock(baseUrl)
.matchHeader('x-route-version', '1')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/preflight')
}
@@ -114,7 +116,7 @@ describe('lib/cloud/api', () => {
.reply(200, AUTH_URLS)
api.clearCache()
sinon.stub(os, 'platform').returns('linux')
sinon.stub(os, 'platform').returns(OS_PLATFORM)
if (this.oldEnv) {
process.env = this.oldEnv
@@ -133,6 +135,7 @@ describe('lib/cloud/api', () => {
afterEach(() => {
api.resetPreflightResult()
sinon.restore()
})
context('.rp', () => {
@@ -195,7 +198,7 @@ describe('lib/cloud/api', () => {
context('.ping', () => {
it('GET /ping', () => {
nock(API_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.get('/ping')
.reply(200, 'OK')
@@ -230,7 +233,7 @@ describe('lib/cloud/api', () => {
nock.cleanAll()
sinon.restore()
sinon.stub(os, 'platform').returns('linux')
sinon.stub(os, 'platform').returns(OS_PLATFORM)
process.env.CYPRESS_CONFIG_ENV = 'production'
process.env.CYPRESS_API_URL = 'https://some.server.com'
@@ -573,7 +576,7 @@ describe('lib/cloud/api', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '4')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/runs', this.buildProps)
.reply(200, {
@@ -647,7 +650,7 @@ describe('lib/cloud/api', () => {
it('POST /runs + returns runId with encryption', function () {
nock.cleanAll()
sinon.restore()
sinon.stub(os, 'platform').returns('linux')
sinon.stub(os, 'platform').returns(OS_PLATFORM)
nock(API_BASEURL)
.get('/capture-protocol/script/protocolStub.js')
@@ -666,7 +669,7 @@ describe('lib/cloud/api', () => {
nock(API_BASEURL)
.defaultReplyHeaders({ 'x-cypress-encrypted': 'true' })
.matchHeader('x-route-version', '4')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/runs')
.reply(200, decryptReqBodyAndRespond({
@@ -749,7 +752,7 @@ describe('lib/cloud/api', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '4')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/runs', this.buildProps)
.reply(200, {
@@ -788,7 +791,7 @@ describe('lib/cloud/api', () => {
it('POST /runs failure formatting', function () {
nock(API_BASEURL)
.matchHeader('x-route-version', '4')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/runs', this.buildProps)
.reply(422, {
@@ -823,7 +826,7 @@ describe('lib/cloud/api', () => {
it('handles timeouts', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '4')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/runs')
.delayConnection(5000)
@@ -893,121 +896,6 @@ describe('lib/cloud/api', () => {
})
})
context('.createInstance', () => {
beforeEach(function () {
Object.defineProperty(process.versions, 'chrome', {
value: '53',
})
this.createProps = {
runId: 'run-id-123',
spec: 'cypress/integration/app_spec.js',
groupId: 'groupId123',
machineId: 'machineId123',
platform: {},
}
this.postProps = _.omit(this.createProps, 'runId')
})
it('POSTs /runs/:id/instances', function () {
os.platform.returns('darwin')
nock(API_BASEURL)
.matchHeader('x-route-version', '5')
.matchHeader('x-cypress-run-id', this.createProps.runId)
.matchHeader('x-cypress-request-attempt', '0')
.matchHeader('x-os-name', 'darwin')
.matchHeader('x-cypress-version', pkg.version)
.post('/runs/run-id-123/instances', this.postProps)
.reply(200, {
instanceId: 'instance-id-123',
})
return api.createInstance(this.createProps)
.get('instanceId')
.then((instanceId) => {
expect(instanceId).to.eq('instance-id-123')
})
})
it('POST /runs/:id/instances failure formatting', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '5')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-cypress-version', pkg.version)
.post('/runs/run-id-123/instances')
.reply(422, {
errors: {
tests: ['is required'],
},
})
return api.createInstance({ runId: 'run-id-123' })
.then(() => {
throw new Error('should have thrown here')
}).catch((err) => {
expect(err.message).to.eq(`\
422
{
"errors": {
"tests": [
"is required"
]
}
}\
`)
})
})
it('handles timeouts', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '5')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-cypress-version', pkg.version)
.post('/runs/run-id-123/instances')
.delayConnection(5000)
.reply(200, {})
return api.createInstance({
runId: 'run-id-123',
timeout: 100,
})
.then(() => {
throw new Error('should have thrown here')
}).catch((err) => {
expect(err.message).to.eq('Error: ESOCKETTIMEDOUT')
})
})
it('sets timeout to 60 seconds', () => {
sinon.stub(api.rp, 'post').resolves({
instanceId: 'instanceId123',
})
return api.createInstance({})
.then(() => {
expect(api.rp.post).to.be.calledWithMatch({ timeout: 60000 })
})
})
it('tags errors', function () {
nock(API_BASEURL)
.matchHeader('authorization', 'Bearer auth-token-123')
.matchHeader('accept-encoding', /gzip/)
.post('/runs/run-id-123/instances', this.postProps)
.reply(500, {})
return api.createInstance(this.createProps)
.then(() => {
throw new Error('should have thrown here')
}).catch((err) => {
expect(err.isApiError).to.be.true
})
})
})
context('.postInstanceTests', () => {
beforeEach(function () {
this.props = {
@@ -1026,7 +914,7 @@ describe('lib/cloud/api', () => {
.matchHeader('x-route-version', '1')
.matchHeader('x-cypress-run-id', this.props.runId)
.matchHeader('x-cypress-request-attempt', '0')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/instances/instance-id-123/tests', this.bodyProps)
.reply(200)
@@ -1037,7 +925,7 @@ describe('lib/cloud/api', () => {
it('PUT /instances/:id failure formatting', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '1')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/instances/instance-id-123/tests')
.reply(422, {
@@ -1067,7 +955,7 @@ describe('lib/cloud/api', () => {
it('handles timeouts', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '1')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/instances/instance-id-123/tests')
.delayConnection(5000)
@@ -1130,7 +1018,7 @@ describe('lib/cloud/api', () => {
.matchHeader('x-route-version', '1')
.matchHeader('x-cypress-run-id', this.updateProps.runId)
.matchHeader('x-cypress-request-attempt', '0')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/instances/instance-id-123/results', this.postProps)
.reply(200)
@@ -1141,7 +1029,7 @@ describe('lib/cloud/api', () => {
it('PUT /instances/:id failure formatting', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '1')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/instances/instance-id-123/results')
.reply(422, {
@@ -1171,7 +1059,7 @@ describe('lib/cloud/api', () => {
it('handles timeouts', () => {
nock(API_BASEURL)
.matchHeader('x-route-version', '1')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post('/instances/instance-id-123/results')
.delayConnection(5000)
@@ -1217,7 +1105,7 @@ describe('lib/cloud/api', () => {
context('.updateInstanceStdout', () => {
it('PUTs /instances/:id/stdout', () => {
nock(API_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-run-id', 'run-id-123')
.matchHeader('x-cypress-request-attempt', '0')
.matchHeader('x-cypress-version', pkg.version)
@@ -1235,7 +1123,7 @@ describe('lib/cloud/api', () => {
it('PUT /instances/:id/stdout failure formatting', () => {
nock(API_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.put('/instances/instance-id-123/stdout')
.reply(422, {
@@ -1264,7 +1152,7 @@ describe('lib/cloud/api', () => {
it('handles timeouts', () => {
nock(API_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.put('/instances/instance-id-123/stdout')
.delayConnection(5000)
@@ -1353,7 +1241,7 @@ describe('lib/cloud/api', () => {
it('POSTs /logout', () => {
nock(CLOUD_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.matchHeader('x-machine-id', 'foo')
.matchHeader('authorization', 'Bearer auth-token-123')
@@ -1366,7 +1254,7 @@ describe('lib/cloud/api', () => {
it('tags errors', () => {
nock(CLOUD_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.matchHeader('x-machine-id', 'foo')
.matchHeader('authorization', 'Bearer auth-token-123')
@@ -1387,7 +1275,7 @@ describe('lib/cloud/api', () => {
beforeEach(function () {
this.setup = (body, authToken, delay = 0) => {
return nock(API_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.matchHeader('authorization', `Bearer ${authToken}`)
.post('/exceptions', body)
@@ -1430,7 +1318,7 @@ describe('lib/cloud/api', () => {
it('tags errors', () => {
nock(API_BASEURL)
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.matchHeader('authorization', 'Bearer auth-token-123')
.matchHeader('accept-encoding', /gzip/)
@@ -1616,7 +1504,7 @@ describe('lib/cloud/api', () => {
.matchHeader('x-route-version', '1')
.matchHeader('x-cypress-run-id', this.artifactOptions.runId)
.matchHeader('x-cypress-request-attempt', '0')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.put('/instances/instance-id-123/artifacts', {
protocol: this.artifactProps.protocol,
@@ -0,0 +1,150 @@
import sinon from 'sinon'
import sinonChai from 'sinon-chai'
import chai, { expect } from 'chai'
import { httpAgent, httpsAgent } from '@packages/network/lib/agent'
import axios, { CreateAxiosDefaults, AxiosInstance } from 'axios'
import { _create } from '../../../../lib/cloud/api/cloud_request'
import app_config from '../../../../config/app.json'
import os from 'os'
import pkg from '@packages/root'
import { transformError } from '../../../../lib/cloud/api/axios_middleware/transform_error'
chai.use(sinonChai)
describe('CloudRequest', () => {
beforeEach(() => {
sinon.stub(axios, 'create').callThrough()
})
afterEach(() => {
(axios.create as sinon.SinonStub).restore()
})
const getCreatedConfig = (): CreateAxiosDefaults => {
const { firstCall: { args: [config] } } = (axios.create as sinon.SinonStub)
return config
}
it('instantiates with network lib http/s agents', () => {
_create()
const cfg = getCreatedConfig()
expect(cfg.httpAgent).to.eq(httpAgent)
expect(cfg.httpsAgent).to.eq(httpsAgent)
})
describe('headers', () => {
const platform = 'sunos'
const version = '0.0.0'
let versionStub
beforeEach(() => {
sinon.stub(os, 'platform').returns(platform)
versionStub = sinon.stub(pkg, 'version').get(() => version)
})
afterEach(() => {
(os.platform as sinon.SinonStub).restore()
versionStub.restore()
})
it('sets exepcted platform, version, and user-agent headers', () => {
_create()
const cfg = getCreatedConfig()
expect(cfg.headers).to.have.property('x-os-name', platform)
expect(cfg.headers).to.have.property('x-cypress-version', version)
expect(cfg.headers).to.have.property('User-Agent', 'cypress/0.0.0')
})
})
describe('interceptors', () => {
let stubbedAxiosInstance: Partial<sinon.SinonStubbedInstance<AxiosInstance>>
beforeEach(() => {
stubbedAxiosInstance = {
interceptors: {
request: {
use: sinon.stub(),
eject: sinon.stub(),
clear: sinon.stub(),
},
response: {
use: sinon.stub(),
eject: sinon.stub(),
clear: sinon.stub(),
},
},
}
;(axios.create as sinon.SinonStub).returns(stubbedAxiosInstance)
_create()
})
it('registers error transformation interceptor', () => {
expect(stubbedAxiosInstance.interceptors?.response.use).to.have.been.calledWith(undefined, transformError)
})
})
;[undefined, 'development', 'test', 'staging', 'production'].forEach((env) => {
describe(`base url for CYPRESS_CONFIG_ENV "${env}"`, () => {
let prevEnv
beforeEach(() => {
prevEnv = process.env.CYPRESS_CONFIG_ENV
if (env) {
process.env.CYPRESS_CONFIG_ENV = env
} else {
delete process.env.CYPRESS_CONFIG_ENV
}
})
afterEach(() => {
if (prevEnv) {
process.env.CYPRESS_CONFIG_ENV = prevEnv
} else {
delete process.env.CYPRESS_CONFIG_ENV
}
})
it('sets to the value defined in app config', () => {
_create()
const cfg = getCreatedConfig()
expect(cfg.baseURL).to.eq(app_config[env ?? 'development']?.api_url)
})
})
describe(`base url for CYPRESS_INTERNAL_ENV "${env}"`, () => {
let prevEnv
beforeEach(() => {
prevEnv = process.env.CYPRESS_INTERNAL_ENV
if (env) {
process.env.CYPRESS_INTERNAL_ENV = env
} else {
delete process.env.CYPRESS_INTERNAL_ENV
}
})
afterEach(() => {
if (prevEnv) {
process.env.CYPRESS_INTERNAL_ENV = prevEnv
} else {
delete process.env.CYPRESS_INTERNAL_ENV
}
})
it('sets to the value defined in app config', () => {
_create()
const cfg = getCreatedConfig()
expect(cfg.baseURL).to.eq(app_config[env ?? 'development']?.api_url)
})
})
})
})
@@ -0,0 +1,107 @@
import chai from 'chai'
import nock from 'nock'
import sinon from 'sinon'
import sinonChai from 'sinon-chai'
import os from 'os'
import pkg from '@packages/root'
import { createInstance } from '../../../../lib/cloud/api/create_instance'
chai.use(sinonChai)
const { expect } = chai
const API_BASEURL = 'http://localhost:1234'
const OS_PLATFORM = 'linux'
context('API createInstance', () => {
let nocked
const runId = 'run-id-123'
const instanceRequestData: Parameters<typeof createInstance>[1] = {
spec: null,
groupId: 'groupId123',
machineId: 'machineId123',
platform: {
osName: OS_PLATFORM,
osVersion: '',
browserName: 'browser',
browserVersion: '1.2.3',
osCpus: [],
osMemory: null,
},
}
const instanceResponseData: Awaited<ReturnType<typeof createInstance>> = {
instanceId: 'instance-id-123',
claimedInstances: 0,
estimatedWallClockDuration: null,
spec: null,
totalInstances: 0,
}
beforeEach(() => {
nocked = nock(API_BASEURL)
.matchHeader('x-cypress-run-id', runId)
// sinon stubbing on the `os` package doesn't work for `createInstance`
//.matchHeader('x-os-name', OS_PLATFORM)
.matchHeader('x-cypress-version', pkg.version)
.post(`/runs/${runId}/instances`)
sinon.stub(os, 'platform').returns(OS_PLATFORM)
})
afterEach(() => {
(os.platform as sinon.SinonStub).restore()
})
describe('when the request succeeds', () => {
beforeEach(() => {
nocked.reply(200, instanceResponseData)
})
it('returns the created instance', async () => {
const response = await createInstance(runId, instanceRequestData)
for (let k in instanceResponseData) {
expect(instanceResponseData[k]).to.eq(response[k])
}
})
})
describe('when the request times out 3 times', () => {
const timeout = 100
beforeEach(() => {
nocked
.times(3)
.delayConnection(5000)
.reply(200, instanceResponseData)
})
it('throws an aggregate error', () => {
return createInstance(runId, instanceRequestData, timeout)
.then(() => {
throw new Error('should have thrown here')
}).catch((err) => {
for (const error of err.errors) {
expect(error.message).to.eq(`timeout of ${timeout}ms exceeded`)
expect(error.isApiError).to.be.true
}
})
})
})
describe('when the request times out once and then succeeds', () => {
beforeEach(() => {
nocked.delayConnection(5000).reply(200, instanceResponseData)
nocked.delayConnection(0).reply(200, instanceResponseData)
})
it('returns the instance response data', async () => {
const data = await createInstance(runId, instanceRequestData, 100)
expect(data).to.deep.eq(instanceResponseData)
})
})
})
@@ -0,0 +1,123 @@
import { expect } from 'chai'
import { installErrorTransform } from '../../../../lib/cloud/api/axios_middleware/transform_error'
import { AxiosError, AxiosResponse, AxiosInstance } from 'axios'
import sinon, { SinonSpy } from 'sinon'
describe('transformError', () => {
const status = 400
const errorData = { message: 'this is an error message' }
const expectedDataMessage = `${status}\n\n{
"message": "this is an error message"
}`
const originalMessage = 'an error occurred'
let transformError: (err: AxiosError | Error & { error?: any, statusCode: number, isApiError?: boolean }) => never
beforeEach(() => {
const mockAxiosInstance: Partial<AxiosInstance> = {
interceptors: {
response: {
use: sinon.spy(),
eject: sinon.spy(),
clear: sinon.spy(),
},
request: {
use: sinon.spy(),
eject: sinon.spy(),
clear: sinon.spy(),
},
},
}
// @ts-expect-error
installErrorTransform(mockAxiosInstance)
const [, secondArg] = (mockAxiosInstance.interceptors?.response.use as SinonSpy).firstCall.args
transformError = secondArg
})
describe('when it receives an axios error', () => {
let err: AxiosError
beforeEach(() => {
err = new AxiosError(originalMessage)
err.status = status
})
describe('and the response has object data', () => {
beforeEach(() => {
err.response = { data: errorData } as AxiosResponse
})
it('throws an error with the expected message', () => {
let thrown
try {
transformError(err)
} catch (e) {
thrown = e
}
expect(thrown).not.to.be.undefined
expect(thrown.message).to.eq(expectedDataMessage)
expect(thrown.isApiError).to.be.true
})
})
describe('and the response does not have object data', () => {
it('re-throws the original error', () => {
let thrown
try {
transformError(err)
} catch (e) {
thrown = e
}
expect(thrown.message).to.eq(err.message)
expect(thrown.isApiError).to.be.true
})
})
})
describe('when it receives a @cypress/request error', () => {
let err: Error & { error?: any, statusCode: number }
beforeEach(() => {
// @ts-expect-error
err = new Error(originalMessage)
err.statusCode = status
})
describe('and that error has an object response', () => {
beforeEach(() => {
err.error = errorData
})
it('throws an error with a formatted message', () => {
let thrown
try {
transformError(err)
} catch (e) {
thrown = e
}
expect(thrown).to.not.be.undefined
expect(thrown.message).to.eq(expectedDataMessage)
expect(thrown.isApiError).to.be.true
})
})
describe('and the response does not have object data', () => {
it('re-throws the original error', () => {
let thrown
try {
transformError(err)
} catch (e) {
thrown = e
}
expect(thrown.message).to.eq(err.message)
expect(thrown.isApiError).to.be.true
})
})
})
})
@@ -416,8 +416,7 @@ describe('lib/modes/record', () => {
return recordMode.createInstance(this.options)
.then(() => {
expect(api.createInstance).to.be.calledWith({
runId: 'run-123',
expect(api.createInstance).to.be.calledWith('run-123', {
groupId: 'group-123',
machineId: 'machine-123',
platform: {},
@@ -1,6 +1,10 @@
import { asyncRetry } from '../../../lib/util/async_retry'
import sinon from 'sinon'
import { expect } from 'chai'
import chai from 'chai'
import sinonChai from '@cypress/sinon-chai'
chai.use(sinonChai)
const { expect } = chai
describe('asyncRetry', () => {
let asyncFn
@@ -58,9 +62,9 @@ describe('asyncRetry', () => {
thrown = e
}
expect(thrown).not.to.be.undefined
expect(thrown.errors.length).to.be.eq(2)
expect(thrown.errors[0].message).to.eq('first call rejection')
expect(thrown.errors[1].message).to.eq('second call rejection')
expect(thrown?.errors.length).to.be.eq(2)
expect(thrown?.errors[0].message).to.eq('first call rejection')
expect(thrown?.errors[1].message).to.eq('second call rejection')
expect(asyncFn).to.have.been.calledTwice
})
})
@@ -74,7 +78,7 @@ describe('asyncRetry', () => {
})
it('throws a non-aggregate error', async () => {
let thrown: Error & { errors?: any[] }
let thrown: Error & { errors?: any[] } | undefined = undefined
try {
await asyncRetry(asyncFn, { maxAttempts: 1 })()
@@ -82,8 +86,8 @@ describe('asyncRetry', () => {
thrown = e
}
expect(thrown.message).to.eq(err.message)
expect(thrown.errors).to.be.undefined
expect(thrown?.message).to.eq(err.message)
expect(thrown?.errors).to.be.undefined
})
})
})
@@ -113,4 +117,30 @@ describe('asyncRetry', () => {
expect(asyncFn).to.have.been.calledTwice
})
})
describe('onRetry option', () => {
let clock: sinon.SinonFakeTimers
let err: Error
beforeEach(() => {
err = new Error('Some Error')
asyncFn.rejects(err)
clock = sinon.useFakeTimers()
})
afterEach(() => {
sinon.restore()
})
it('is called with the delay and the error that occurred, before the next retry', async () => {
const onRetryFn = sinon.stub<[number, unknown], void>()
const delay = 500
const p = asyncRetry(asyncFn, { maxAttempts: 2, retryDelay: () => delay, onRetry: onRetryFn })().catch((e) => {})
await clock.tickAsync(1)
expect(onRetryFn).to.have.been.calledOnceWith(delay, err)
await clock.runAllAsync()
await p
})
})
})
+1 -1
View File
@@ -40,7 +40,7 @@ declare module 'http' {
proxy: Optional<string>
servername: Optional<string>
socket: Optional<Socket>
uri: Url
uri?: Url
}
interface OutgoingMessage {
+5 -5
View File
@@ -1169,7 +1169,7 @@ exports['e2e record api interaction errors create instance 500 does not proceed
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Request failed with status code 500
Because you passed the --parallel flag, this run cannot proceed since it requires a valid response from our servers.
@@ -1195,7 +1195,7 @@ exports['e2e record api interaction errors create instance 500 without paralleli
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Request failed with status code 500
Because you passed the --record flag, this run cannot proceed since it requires a valid response from our servers.
@@ -1413,7 +1413,7 @@ exports['e2e record api interaction errors create instance errors and exits on c
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Request failed with status code 500
Because you passed the --record flag, this run cannot proceed since it requires a valid response from our servers.
@@ -4420,9 +4420,9 @@ We will retry 1 more time in X second(s)...
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Request failed with status code 500
We will retry 3 more times in X second(s)...
We will retry 2 more times in ...
+68 -1
View File
@@ -69,6 +69,7 @@
"./packages/server/lib/socket-ct.ts",
"./packages/server/lib/util/process_profiler.ts",
"./packages/server/lib/util/suppress_warnings.js",
"./packages/server/node_modules/axios/lib/adapters/http.js",
"./packages/server/node_modules/ci-info/index.js",
"./packages/server/node_modules/glob/node_modules/minimatch/minimatch.js",
"./packages/server/node_modules/graceful-fs/polyfills.js",
@@ -756,6 +757,7 @@
"./packages/server/lib/browsers/firefox.ts",
"./packages/server/lib/browsers/memory/index.ts",
"./packages/server/lib/cache.js",
"./packages/server/lib/cloud/api/cloud_request.ts",
"./packages/server/lib/cloud/api/get_and_initialize_studio_manager.ts",
"./packages/server/lib/cloud/api/index.ts",
"./packages/server/lib/cloud/api/put_protocol_artifact.ts",
@@ -780,6 +782,21 @@
"./packages/server/lib/util/fs.ts",
"./packages/server/lib/util/glob.js",
"./packages/server/lib/video_capture.ts",
"./packages/server/node_modules/axios/lib/adapters/adapters.js",
"./packages/server/node_modules/axios/lib/adapters/http.js",
"./packages/server/node_modules/axios/lib/axios.js",
"./packages/server/node_modules/axios/lib/cancel/CanceledError.js",
"./packages/server/node_modules/axios/lib/core/Axios.js",
"./packages/server/node_modules/axios/lib/core/AxiosError.js",
"./packages/server/node_modules/axios/lib/core/AxiosHeaders.js",
"./packages/server/node_modules/axios/lib/defaults/index.js",
"./packages/server/node_modules/axios/lib/helpers/AxiosTransformStream.js",
"./packages/server/node_modules/axios/lib/helpers/ZlibHeaderTransformStream.js",
"./packages/server/node_modules/axios/lib/helpers/cookies.js",
"./packages/server/node_modules/axios/lib/helpers/formDataToStream.js",
"./packages/server/node_modules/axios/lib/helpers/isURLSameOrigin.js",
"./packages/server/node_modules/axios/lib/platform/node/index.js",
"./packages/server/node_modules/axios/lib/utils.js",
"./packages/server/node_modules/body-parser/index.js",
"./packages/server/node_modules/body-parser/node_modules/debug/src/browser.js",
"./packages/server/node_modules/body-parser/node_modules/debug/src/index.js",
@@ -1069,6 +1086,8 @@
"./node_modules/@cypress/commit-info/src/index.js",
"./node_modules/@cypress/commit-info/src/utils.js",
"./node_modules/@cypress/get-windows-proxy/node_modules/debug/src/common.js",
"./node_modules/@cypress/get-windows-proxy/node_modules/registry-js/dist/lib/index.js",
"./node_modules/@cypress/get-windows-proxy/node_modules/registry-js/dist/lib/registry.js",
"./node_modules/@cypress/get-windows-proxy/src/index.js",
"./node_modules/@cypress/parse-domain/build/tries/icann.complete.json",
"./node_modules/@cypress/parse-domain/build/tries/private.complete.json",
@@ -2771,6 +2790,7 @@
"./node_modules/pretty-bytes/index.js",
"./node_modules/proxy-addr/index.js",
"./node_modules/proxy-addr/node_modules/ipaddr.js/lib/ipaddr.js",
"./node_modules/proxy-from-env/index.js",
"./node_modules/pseudomap/pseudomap.js",
"./node_modules/psl/data/rules.json",
"./node_modules/psl/index.js",
@@ -3222,6 +3242,8 @@
"./node_modules/recast/parsers/babel.js",
"./node_modules/recast/parsers/esprima.js",
"./node_modules/recast/parsers/typescript.js",
"./node_modules/registry-js/dist/lib/index.js",
"./node_modules/registry-js/dist/lib/registry.js",
"./node_modules/request-promise-core/configure/request2.js",
"./node_modules/request-promise-core/errors.js",
"./node_modules/request-promise-core/lib/errors.js",
@@ -3960,6 +3982,10 @@
"./packages/server/lib/browsers/webdriver/index.ts",
"./packages/server/lib/browsers/webkit-automation.ts",
"./packages/server/lib/browsers/webkit.ts",
"./packages/server/lib/cloud/api/axios_middleware/logging.ts",
"./packages/server/lib/cloud/api/axios_middleware/transform_error.ts",
"./packages/server/lib/cloud/api/create_instance.ts",
"./packages/server/lib/cloud/api/get_app_studio.ts",
"./packages/server/lib/cloud/api/scrub_url.ts",
"./packages/server/lib/cloud/artifacts/artifact.ts",
"./packages/server/lib/cloud/artifacts/file_upload_strategy.ts",
@@ -4064,6 +4090,47 @@
"./packages/server/lib/util/tests_utils.ts",
"./packages/server/lib/util/trash.js",
"./packages/server/lib/util/tty.js",
"./packages/server/node_modules/ansi-regex/index.js",
"./packages/server/node_modules/axios/index.js",
"./packages/server/node_modules/axios/lib/adapters/fetch.js",
"./packages/server/node_modules/axios/lib/adapters/xhr.js",
"./packages/server/node_modules/axios/lib/cancel/CancelToken.js",
"./packages/server/node_modules/axios/lib/cancel/isCancel.js",
"./packages/server/node_modules/axios/lib/core/InterceptorManager.js",
"./packages/server/node_modules/axios/lib/core/buildFullPath.js",
"./packages/server/node_modules/axios/lib/core/dispatchRequest.js",
"./packages/server/node_modules/axios/lib/core/mergeConfig.js",
"./packages/server/node_modules/axios/lib/core/settle.js",
"./packages/server/node_modules/axios/lib/core/transformData.js",
"./packages/server/node_modules/axios/lib/defaults/transitional.js",
"./packages/server/node_modules/axios/lib/env/data.js",
"./packages/server/node_modules/axios/lib/helpers/AxiosURLSearchParams.js",
"./packages/server/node_modules/axios/lib/helpers/HttpStatusCode.js",
"./packages/server/node_modules/axios/lib/helpers/bind.js",
"./packages/server/node_modules/axios/lib/helpers/buildURL.js",
"./packages/server/node_modules/axios/lib/helpers/callbackify.js",
"./packages/server/node_modules/axios/lib/helpers/combineURLs.js",
"./packages/server/node_modules/axios/lib/helpers/composeSignals.js",
"./packages/server/node_modules/axios/lib/helpers/formDataToJSON.js",
"./packages/server/node_modules/axios/lib/helpers/fromDataURI.js",
"./packages/server/node_modules/axios/lib/helpers/isAbsoluteURL.js",
"./packages/server/node_modules/axios/lib/helpers/isAxiosError.js",
"./packages/server/node_modules/axios/lib/helpers/parseHeaders.js",
"./packages/server/node_modules/axios/lib/helpers/parseProtocol.js",
"./packages/server/node_modules/axios/lib/helpers/progressEventReducer.js",
"./packages/server/node_modules/axios/lib/helpers/readBlob.js",
"./packages/server/node_modules/axios/lib/helpers/resolveConfig.js",
"./packages/server/node_modules/axios/lib/helpers/speedometer.js",
"./packages/server/node_modules/axios/lib/helpers/spread.js",
"./packages/server/node_modules/axios/lib/helpers/throttle.js",
"./packages/server/node_modules/axios/lib/helpers/toFormData.js",
"./packages/server/node_modules/axios/lib/helpers/toURLEncodedForm.js",
"./packages/server/node_modules/axios/lib/helpers/trackStream.js",
"./packages/server/node_modules/axios/lib/helpers/validator.js",
"./packages/server/node_modules/axios/lib/platform/common/utils.js",
"./packages/server/node_modules/axios/lib/platform/index.js",
"./packages/server/node_modules/axios/lib/platform/node/classes/FormData.js",
"./packages/server/node_modules/axios/lib/platform/node/classes/URLSearchParams.js",
"./packages/server/node_modules/body-parser/lib/read.js",
"./packages/server/node_modules/body-parser/lib/types/json.js",
"./packages/server/node_modules/body-parser/lib/types/raw.js",
@@ -4244,5 +4311,5 @@
"./tooling/v8-snapshot/cache/darwin/snapshot-entry.js"
],
"deferredHashFile": "yarn.lock",
"deferredHash": "f6564127cd3ef719f778f1b7b8f506ad7338b365adaa350d2e79a13d0ab5ccdb"
"deferredHash": "e06c1a6deefc2c8da167f59309cec0febfafc6a559f03a62d9bcfa8654f9393c"
}
@@ -73,4 +73,5 @@ export default [
'packages/server/node_modules/ci-info/index.js',
'node_modules/@babel/traverse/lib/index.js',
'node_modules/@babel/types/lib/definitions/index.js',
'packages/server/node_modules/axios/lib/adapters/http.js',
]
+10 -1
View File
@@ -10827,7 +10827,7 @@ axios@0.21.2:
dependencies:
follow-redirects "^1.14.0"
axios@1.7.7, axios@^1.7.4:
axios@1.7.7:
version "1.7.7"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==
@@ -10836,6 +10836,15 @@ axios@1.7.7, axios@^1.7.4:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axios@^1.7.4, axios@^1.7.9:
version "1.7.9"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a"
integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"