mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-03 13:30:26 -05:00
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:
@@ -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",
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
@@ -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)),
|
||||
},
|
||||
)
|
||||
},
|
||||
})()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Vendored
+1
-1
@@ -40,7 +40,7 @@ declare module 'http' {
|
||||
proxy: Optional<string>
|
||||
servername: Optional<string>
|
||||
socket: Optional<Socket>
|
||||
uri: Url
|
||||
uri?: Url
|
||||
}
|
||||
|
||||
interface OutgoingMessage {
|
||||
|
||||
@@ -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
@@ -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',
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user