mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-09 08:00:14 -06:00
refactor: extract artifact upload process from lib/modes/record.js (#29240)
* refactor record.js to extract upload logic into ts - streamlines the main uploadArtifact fn - extracts artifact specific logic to artifact classes - fully defines types for upload processing and reporting * tweak refactors so system tests produce same snapshots * some todos, fixes exports/imports from api/index.ts * fix api export so it can be imported by ts files * cleans up types * extracting artifact metadata from options logs to debug but does not throw if errors are encountered * fix type imports in print-run * fix debug formatting for artifacts * fix reporting successful protocol uploads * change inheritence to strategy * rm empty file * Update packages/server/lib/cloud/artifacts/upload_artifacts.ts * makes protocolManager optional to uploadArtifacts, fixes conditional accessor in protocol fatal error report * missed a potentially undef * convert to frozen object / keyof instead of string composition for artifact kinds --------- Co-authored-by: Ryan Manuel <ryanm@cypress.io> Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
This commit is contained in:
@@ -58,6 +58,7 @@ export interface CypressRequestOptions extends OptionsWithUrl {
|
||||
cacheable?: boolean
|
||||
}
|
||||
|
||||
// TODO: migrate to fetch from @cypress/request
|
||||
const rp = request.defaults((params: CypressRequestOptions, callback) => {
|
||||
let resp
|
||||
|
||||
@@ -274,22 +275,23 @@ type CreateRunResponse = {
|
||||
} | undefined
|
||||
}
|
||||
|
||||
type ArtifactMetadata = {
|
||||
export type ArtifactMetadata = {
|
||||
url: string
|
||||
fileSize?: number
|
||||
fileSize?: number | bigint
|
||||
uploadDuration?: number
|
||||
success: boolean
|
||||
error?: string
|
||||
errorStack?: string
|
||||
}
|
||||
|
||||
type ProtocolMetadata = ArtifactMetadata & {
|
||||
export type ProtocolMetadata = ArtifactMetadata & {
|
||||
specAccess?: {
|
||||
size: bigint
|
||||
offset: bigint
|
||||
size: number
|
||||
offset: number
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateInstanceArtifactsPayload = {
|
||||
export type UpdateInstanceArtifactsPayload = {
|
||||
screenshots: ArtifactMetadata[]
|
||||
video?: ArtifactMetadata
|
||||
protocol?: ProtocolMetadata
|
||||
@@ -298,7 +300,7 @@ type UpdateInstanceArtifactsPayload = {
|
||||
type UpdateInstanceArtifactsOptions = {
|
||||
runId: string
|
||||
instanceId: string
|
||||
timeout: number | undefined
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
let preflightResult = {
|
||||
@@ -307,7 +309,10 @@ let preflightResult = {
|
||||
|
||||
let recordRoutes = apiRoutes
|
||||
|
||||
module.exports = {
|
||||
// Potential todos: Refactor to named exports, refactor away from `this.` in exports,
|
||||
// move individual exports to their own files & convert this to barrelfile
|
||||
|
||||
export default {
|
||||
rp,
|
||||
|
||||
// For internal testing
|
||||
@@ -400,8 +405,10 @@ module.exports = {
|
||||
let script
|
||||
|
||||
try {
|
||||
if (captureProtocolUrl || process.env.CYPRESS_LOCAL_PROTOCOL_PATH) {
|
||||
script = await this.getCaptureProtocolScript(captureProtocolUrl || process.env.CYPRESS_LOCAL_PROTOCOL_PATH)
|
||||
const protocolUrl = captureProtocolUrl || process.env.CYPRESS_LOCAL_PROTOCOL_PATH
|
||||
|
||||
if (protocolUrl) {
|
||||
script = await this.getCaptureProtocolScript(protocolUrl)
|
||||
}
|
||||
} catch (e) {
|
||||
debugProtocol('Error downloading capture code', e)
|
||||
|
||||
113
packages/server/lib/cloud/artifacts/artifact.ts
Normal file
113
packages/server/lib/cloud/artifacts/artifact.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import Debug from 'debug'
|
||||
import { performance } from 'perf_hooks'
|
||||
|
||||
const debug = Debug('cypress:server:cloud:artifact')
|
||||
|
||||
const isAggregateError = (err: any): err is AggregateError => {
|
||||
return !!err.errors
|
||||
}
|
||||
|
||||
export const ArtifactKinds = Object.freeze({
|
||||
VIDEO: 'video',
|
||||
SCREENSHOTS: 'screenshots',
|
||||
PROTOCOL: 'protocol',
|
||||
})
|
||||
|
||||
type ArtifactKind = typeof ArtifactKinds[keyof typeof ArtifactKinds]
|
||||
|
||||
export interface IArtifact {
|
||||
reportKey: ArtifactKind
|
||||
uploadUrl: string
|
||||
filePath: string
|
||||
fileSize: number | bigint
|
||||
upload: () => Promise<ArtifactUploadResult>
|
||||
}
|
||||
|
||||
export interface ArtifactUploadResult {
|
||||
success: boolean
|
||||
error?: Error | string
|
||||
url: string
|
||||
pathToFile: string
|
||||
fileSize?: number | bigint
|
||||
key: ArtifactKind
|
||||
errorStack?: string
|
||||
allErrors?: Error[]
|
||||
specAccess?: {
|
||||
offset: number
|
||||
size: number
|
||||
}
|
||||
uploadDuration?: number
|
||||
}
|
||||
|
||||
export type ArtifactUploadStrategy<T> = (filePath: string, uploadUrl: string, fileSize: number | bigint) => T
|
||||
|
||||
export class Artifact<T extends ArtifactUploadStrategy<UploadResponse>, UploadResponse extends Promise<any> = Promise<{}>> {
|
||||
constructor (
|
||||
public reportKey: ArtifactKind,
|
||||
public readonly filePath: string,
|
||||
public readonly uploadUrl: string,
|
||||
public readonly fileSize: number | bigint,
|
||||
private uploadStrategy: T,
|
||||
) {
|
||||
}
|
||||
|
||||
public async upload (): Promise<ArtifactUploadResult> {
|
||||
const startTime = performance.now()
|
||||
|
||||
this.debug('upload starting')
|
||||
|
||||
try {
|
||||
const response = await this.uploadStrategy(this.filePath, this.uploadUrl, this.fileSize)
|
||||
|
||||
this.debug('upload succeeded: %O', response)
|
||||
|
||||
return this.composeSuccessResult(response ?? {}, performance.now() - startTime)
|
||||
} catch (e) {
|
||||
this.debug('upload failed: %O', e)
|
||||
|
||||
return this.composeFailureResult(e, performance.now() - startTime)
|
||||
}
|
||||
}
|
||||
|
||||
private debug (formatter: string = '', ...args: (string | object | number)[]) {
|
||||
if (!debug.enabled) return
|
||||
|
||||
debug(`%s: %s -> %s (%dB) ${formatter}`, this.reportKey, this.filePath, this.uploadUrl, this.fileSize, ...args)
|
||||
}
|
||||
|
||||
private commonResultFields (): Pick<ArtifactUploadResult, 'url' | 'pathToFile' | 'fileSize' | 'key'> {
|
||||
return {
|
||||
key: this.reportKey,
|
||||
url: this.uploadUrl,
|
||||
pathToFile: this.filePath,
|
||||
fileSize: this.fileSize,
|
||||
}
|
||||
}
|
||||
|
||||
protected composeSuccessResult<T extends Object = {}> (response: T, uploadDuration: number): ArtifactUploadResult {
|
||||
return {
|
||||
...response,
|
||||
...this.commonResultFields(),
|
||||
success: true,
|
||||
uploadDuration,
|
||||
}
|
||||
}
|
||||
|
||||
protected composeFailureResult<T extends Error> (err: T, uploadDuration: number): ArtifactUploadResult {
|
||||
const errorReport = isAggregateError(err) ? {
|
||||
error: err.errors[err.errors.length - 1].message,
|
||||
errorStack: err.errors[err.errors.length - 1].stack,
|
||||
allErrors: err.errors,
|
||||
} : {
|
||||
error: err.message,
|
||||
errorStack: err.stack,
|
||||
}
|
||||
|
||||
return {
|
||||
...errorReport,
|
||||
...this.commonResultFields(),
|
||||
success: false,
|
||||
uploadDuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { sendFile } from '../upload/send_file'
|
||||
import type { ArtifactUploadStrategy } from './artifact'
|
||||
|
||||
export const fileUploadStrategy: ArtifactUploadStrategy<Promise<any>> = (filePath, uploadUrl) => {
|
||||
return sendFile(filePath, uploadUrl)
|
||||
}
|
||||
61
packages/server/lib/cloud/artifacts/protocol_artifact.ts
Normal file
61
packages/server/lib/cloud/artifacts/protocol_artifact.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import fs from 'fs/promises'
|
||||
import type { ProtocolManager } from '../protocol'
|
||||
import { IArtifact, ArtifactUploadStrategy, ArtifactUploadResult, Artifact, ArtifactKinds } from './artifact'
|
||||
|
||||
interface ProtocolUploadStrategyResult {
|
||||
success: boolean
|
||||
fileSize: number | bigint
|
||||
specAccess: {
|
||||
offset: number
|
||||
size: number
|
||||
}
|
||||
}
|
||||
|
||||
const createProtocolUploadStrategy = (protocolManager: ProtocolManager) => {
|
||||
const strategy: ArtifactUploadStrategy<Promise<ProtocolUploadStrategyResult | {}>> =
|
||||
async (filePath, uploadUrl, fileSize) => {
|
||||
const fatalError = protocolManager.getFatalError()
|
||||
|
||||
if (fatalError) {
|
||||
throw fatalError.error
|
||||
}
|
||||
|
||||
const res = await protocolManager.uploadCaptureArtifact({ uploadUrl, fileSize, filePath })
|
||||
|
||||
return res ?? {}
|
||||
}
|
||||
|
||||
return strategy
|
||||
}
|
||||
|
||||
export const createProtocolArtifact = async (filePath: string, uploadUrl: string, protocolManager: ProtocolManager): Promise<IArtifact> => {
|
||||
const { size } = await fs.stat(filePath)
|
||||
|
||||
return new Artifact('protocol', filePath, uploadUrl, size, createProtocolUploadStrategy(protocolManager))
|
||||
}
|
||||
|
||||
export const composeProtocolErrorReportFromOptions = async ({
|
||||
protocolManager,
|
||||
protocolCaptureMeta,
|
||||
captureUploadUrl,
|
||||
}: {
|
||||
protocolManager?: ProtocolManager
|
||||
protocolCaptureMeta: { url?: string, disabledMessage?: string }
|
||||
captureUploadUrl?: string
|
||||
}): Promise<ArtifactUploadResult> => {
|
||||
const url = captureUploadUrl || protocolCaptureMeta.url
|
||||
const pathToFile = protocolManager?.getArchivePath()
|
||||
const fileSize = pathToFile ? (await fs.stat(pathToFile))?.size : 0
|
||||
|
||||
const fatalError = protocolManager?.getFatalError()
|
||||
|
||||
return {
|
||||
key: ArtifactKinds.PROTOCOL,
|
||||
url: url ?? 'UNKNOWN',
|
||||
pathToFile: pathToFile ?? 'UNKNOWN',
|
||||
fileSize,
|
||||
success: false,
|
||||
error: fatalError?.error.message || 'UNKNOWN',
|
||||
errorStack: fatalError?.error.stack || 'UNKNOWN',
|
||||
}
|
||||
}
|
||||
44
packages/server/lib/cloud/artifacts/screenshot_artifact.ts
Normal file
44
packages/server/lib/cloud/artifacts/screenshot_artifact.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import fs from 'fs/promises'
|
||||
import Debug from 'debug'
|
||||
import { Artifact, IArtifact, ArtifactKinds } from './artifact'
|
||||
import { fileUploadStrategy } from './file_upload_strategy'
|
||||
|
||||
const debug = Debug('cypress:server:cloud:artifacts:screenshot')
|
||||
|
||||
const createScreenshotArtifact = async (filePath: string, uploadUrl: string): Promise<IArtifact | undefined> => {
|
||||
try {
|
||||
const { size } = await fs.stat(filePath)
|
||||
|
||||
return new Artifact(ArtifactKinds.SCREENSHOTS, filePath, uploadUrl, size, fileUploadStrategy)
|
||||
} catch (e) {
|
||||
debug('Error creating screenshot artifact: %O', e)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export const createScreenshotArtifactBatch = (
|
||||
screenshotUploadUrls: {screenshotId: string, uploadUrl: string}[],
|
||||
screenshotFiles: {screenshotId: string, path: string}[],
|
||||
): Promise<IArtifact[]> => {
|
||||
const correlatedPaths = screenshotUploadUrls.map(({ screenshotId, uploadUrl }) => {
|
||||
const correlatedFilePath = screenshotFiles.find((pathPair) => {
|
||||
return pathPair.screenshotId === screenshotId
|
||||
})?.path
|
||||
|
||||
return correlatedFilePath ? {
|
||||
filePath: correlatedFilePath,
|
||||
uploadUrl,
|
||||
} : undefined
|
||||
}).filter((pair): pair is { filePath: string, uploadUrl: string } => {
|
||||
return !!pair
|
||||
})
|
||||
|
||||
return Promise.all(correlatedPaths.map(({ filePath, uploadUrl }) => {
|
||||
return createScreenshotArtifact(filePath, uploadUrl)
|
||||
})).then((artifacts) => {
|
||||
return artifacts.filter((artifact): artifact is IArtifact => {
|
||||
return !!artifact
|
||||
})
|
||||
})
|
||||
}
|
||||
204
packages/server/lib/cloud/artifacts/upload_artifacts.ts
Normal file
204
packages/server/lib/cloud/artifacts/upload_artifacts.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import Debug from 'debug'
|
||||
import type ProtocolManager from '../protocol'
|
||||
import api from '../api'
|
||||
import { logUploadManifest, logUploadResults, beginUploadActivityOutput } from '../../util/print-run'
|
||||
import type { UpdateInstanceArtifactsPayload, ArtifactMetadata, ProtocolMetadata } from '../api'
|
||||
import * as errors from '../../errors'
|
||||
import exception from '../exception'
|
||||
import { IArtifact, ArtifactUploadResult, ArtifactKinds } from './artifact'
|
||||
import { createScreenshotArtifactBatch } from './screenshot_artifact'
|
||||
import { createVideoArtifact } from './video_artifact'
|
||||
import { createProtocolArtifact, composeProtocolErrorReportFromOptions } from './protocol_artifact'
|
||||
|
||||
const debug = Debug('cypress:server:cloud:artifacts')
|
||||
|
||||
const toUploadReportPayload = (acc: {
|
||||
screenshots: ArtifactMetadata[]
|
||||
video?: ArtifactMetadata
|
||||
protocol?: ProtocolMetadata
|
||||
}, { key, ...report }: ArtifactUploadResult): UpdateInstanceArtifactsPayload => {
|
||||
if (key === ArtifactKinds.PROTOCOL) {
|
||||
let { error, errorStack, allErrors } = report
|
||||
|
||||
if (allErrors) {
|
||||
error = `Failed to upload Test Replay after ${allErrors.length} attempts. Errors: ${allErrors.map((error) => error.message).join(', ')}`
|
||||
errorStack = allErrors.map((error) => error.stack).join(', ')
|
||||
} else if (error) {
|
||||
error = `Failed to upload Test Replay: ${error}`
|
||||
}
|
||||
|
||||
debug('protocol report %O', report)
|
||||
|
||||
return {
|
||||
...acc,
|
||||
protocol: {
|
||||
...report,
|
||||
error,
|
||||
errorStack,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: (key === 'screenshots') ? [...acc.screenshots, report] : report,
|
||||
}
|
||||
}
|
||||
|
||||
type UploadArtifactOptions = {
|
||||
protocolManager?: ProtocolManager
|
||||
videoUploadUrl?: string
|
||||
video?: string // filepath to the video artifact
|
||||
screenshots?: {
|
||||
screenshotId: string
|
||||
path: string
|
||||
}[]
|
||||
screenshotUploadUrls?: {
|
||||
screenshotId: string
|
||||
uploadUrl: string
|
||||
}[]
|
||||
captureUploadUrl?: string
|
||||
protocolCaptureMeta: {
|
||||
url?: string
|
||||
disabledMessage?: string
|
||||
}
|
||||
quiet?: boolean
|
||||
runId: string
|
||||
instanceId: string
|
||||
spec: any
|
||||
platform: any
|
||||
projectId: any
|
||||
}
|
||||
|
||||
const extractArtifactsFromOptions = async ({
|
||||
video, videoUploadUrl, screenshots, screenshotUploadUrls, captureUploadUrl, protocolCaptureMeta, protocolManager,
|
||||
}: Pick<UploadArtifactOptions,
|
||||
'video' | 'videoUploadUrl' |
|
||||
'screenshots' | 'screenshotUploadUrls' |
|
||||
'captureUploadUrl' | 'protocolManager' | 'protocolCaptureMeta'
|
||||
>): Promise<IArtifact[]> => {
|
||||
const artifacts: IArtifact[] = []
|
||||
|
||||
if (videoUploadUrl && video) {
|
||||
try {
|
||||
artifacts.push(await createVideoArtifact(video, videoUploadUrl))
|
||||
} catch (e) {
|
||||
debug('Error creating video artifact: %O', e)
|
||||
}
|
||||
}
|
||||
|
||||
debug('screenshot metadata: %O', { screenshotUploadUrls, screenshots })
|
||||
debug('found screenshot filenames: %o', screenshots)
|
||||
if (screenshots?.length && screenshotUploadUrls?.length) {
|
||||
const screenshotArtifacts = await createScreenshotArtifactBatch(screenshotUploadUrls, screenshots)
|
||||
|
||||
screenshotArtifacts.forEach((screenshot) => {
|
||||
artifacts.push(screenshot)
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const protocolFilePath = protocolManager?.getArchivePath()
|
||||
|
||||
const protocolUploadUrl = captureUploadUrl || protocolCaptureMeta.url
|
||||
|
||||
debug('should add protocol artifact? %o, %o, %O', protocolFilePath, protocolUploadUrl, protocolManager)
|
||||
if (protocolManager && protocolFilePath && protocolUploadUrl) {
|
||||
artifacts.push(await createProtocolArtifact(protocolFilePath, protocolUploadUrl, protocolManager))
|
||||
}
|
||||
} catch (e) {
|
||||
debug('Error creating protocol artifact: %O', e)
|
||||
}
|
||||
|
||||
return artifacts
|
||||
}
|
||||
|
||||
export const uploadArtifacts = async (options: UploadArtifactOptions) => {
|
||||
const { protocolManager, protocolCaptureMeta, quiet, runId, instanceId, spec, platform, projectId } = options
|
||||
|
||||
const priority = {
|
||||
[ArtifactKinds.VIDEO]: 0,
|
||||
[ArtifactKinds.SCREENSHOTS]: 1,
|
||||
[ArtifactKinds.PROTOCOL]: 2,
|
||||
}
|
||||
|
||||
const artifacts = (await extractArtifactsFromOptions(options)).sort((a, b) => {
|
||||
return priority[a.reportKey] - priority[b.reportKey]
|
||||
})
|
||||
|
||||
let uploadReport: UpdateInstanceArtifactsPayload
|
||||
|
||||
if (!quiet) {
|
||||
logUploadManifest(artifacts, protocolCaptureMeta, protocolManager?.getFatalError())
|
||||
}
|
||||
|
||||
debug('preparing to upload artifacts: %O', artifacts)
|
||||
|
||||
let stopUploadActivityOutput: () => void | undefined
|
||||
|
||||
if (!quiet && artifacts.length) {
|
||||
stopUploadActivityOutput = beginUploadActivityOutput()
|
||||
}
|
||||
|
||||
try {
|
||||
const uploadResults = await Promise.all(artifacts.map((artifact) => artifact.upload())).finally(() => {
|
||||
if (stopUploadActivityOutput) {
|
||||
stopUploadActivityOutput()
|
||||
}
|
||||
})
|
||||
|
||||
if (!quiet && uploadResults.length) {
|
||||
logUploadResults(uploadResults, protocolManager?.getFatalError())
|
||||
}
|
||||
|
||||
const protocolFatalError = protocolManager?.getFatalError()
|
||||
|
||||
/**
|
||||
* Protocol instances with fatal errors prior to uploading will not have an uploadResult,
|
||||
* but we still want to report them to updateInstanceArtifacts
|
||||
*/
|
||||
if (!uploadResults.find((result: ArtifactUploadResult) => {
|
||||
return result.key === ArtifactKinds.PROTOCOL
|
||||
}) && protocolFatalError) {
|
||||
uploadResults.push(await composeProtocolErrorReportFromOptions(options))
|
||||
}
|
||||
|
||||
uploadReport = uploadResults.reduce(toUploadReportPayload, { video: undefined, screenshots: [], protocol: undefined })
|
||||
} catch (err) {
|
||||
errors.warning('CLOUD_CANNOT_UPLOAD_ARTIFACTS', err)
|
||||
|
||||
return exception.create(err)
|
||||
}
|
||||
|
||||
debug('checking for protocol errors', protocolManager?.hasErrors())
|
||||
if (protocolManager) {
|
||||
try {
|
||||
await protocolManager.reportNonFatalErrors({
|
||||
specName: spec.name,
|
||||
osName: platform.osName,
|
||||
projectSlug: projectId,
|
||||
})
|
||||
} catch (err) {
|
||||
debug('Failed to send protocol errors %O', err)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
debug('upload report: %O', uploadReport)
|
||||
const res = await api.updateInstanceArtifacts({
|
||||
runId, instanceId,
|
||||
}, uploadReport)
|
||||
|
||||
return res
|
||||
} catch (err) {
|
||||
debug('failed updating artifact status %o', {
|
||||
stack: err.stack,
|
||||
})
|
||||
|
||||
errors.warning('CLOUD_CANNOT_UPLOAD_ARTIFACTS_PROTOCOL', err)
|
||||
|
||||
if (err.statusCode !== 503) {
|
||||
return exception.create(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
9
packages/server/lib/cloud/artifacts/video_artifact.ts
Normal file
9
packages/server/lib/cloud/artifacts/video_artifact.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import fs from 'fs/promises'
|
||||
import { Artifact, IArtifact, ArtifactKinds } from './artifact'
|
||||
import { fileUploadStrategy } from './file_upload_strategy'
|
||||
|
||||
export const createVideoArtifact = async (filePath: string, uploadUrl: string): Promise<IArtifact> => {
|
||||
const { size } = await fs.stat(filePath)
|
||||
|
||||
return new Artifact(ArtifactKinds.VIDEO, filePath, uploadUrl, size, fileUploadStrategy)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash'
|
||||
const Promise = require('bluebird')
|
||||
const pkg = require('@packages/root')
|
||||
const api = require('./api')
|
||||
const api = require('./api').default
|
||||
const user = require('./user')
|
||||
const system = require('../util/system')
|
||||
|
||||
|
||||
@@ -259,6 +259,10 @@ export class ProtocolManager implements ProtocolManagerShape {
|
||||
return this._errors.filter((e) => !e.fatal)
|
||||
}
|
||||
|
||||
getArchivePath (): string | undefined {
|
||||
return this._archivePath
|
||||
}
|
||||
|
||||
async getArchiveInfo (): Promise<{ filePath: string, fileSize: number } | void> {
|
||||
const archivePath = this._archivePath
|
||||
|
||||
@@ -275,9 +279,9 @@ export class ProtocolManager implements ProtocolManagerShape {
|
||||
|
||||
async uploadCaptureArtifact ({ uploadUrl, fileSize, filePath }: CaptureArtifact): Promise<{
|
||||
success: boolean
|
||||
fileSize: number
|
||||
specAccess?: ReturnType<AppCaptureProtocolInterface['getDbMetadata']>
|
||||
} | void> {
|
||||
fileSize: number | bigint
|
||||
specAccess: ReturnType<AppCaptureProtocolInterface['getDbMetadata']>
|
||||
} | undefined> {
|
||||
if (!this._protocol || !filePath || !this._db) {
|
||||
debug('not uploading due to one of the following being falsy: %O', {
|
||||
_protocol: !!this._protocol,
|
||||
@@ -296,7 +300,7 @@ export class ProtocolManager implements ProtocolManagerShape {
|
||||
return {
|
||||
fileSize,
|
||||
success: true,
|
||||
specAccess: this._protocol?.getDbMetadata(),
|
||||
specAccess: this._protocol.getDbMetadata(),
|
||||
}
|
||||
} catch (e) {
|
||||
if (CAPTURE_ERRORS) {
|
||||
@@ -304,10 +308,13 @@ export class ProtocolManager implements ProtocolManagerShape {
|
||||
error: e,
|
||||
captureMethod: 'uploadCaptureArtifact',
|
||||
fatal: true,
|
||||
isUploadError: true,
|
||||
})
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
return
|
||||
} finally {
|
||||
if (DELETE_DB) {
|
||||
await fs.unlink(filePath).catch((e) => {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
const rp = require('@cypress/request-promise')
|
||||
const { fs } = require('../util/fs')
|
||||
|
||||
export = {
|
||||
send (pathToFile: string, url: string) {
|
||||
return fs
|
||||
.readFileAsync(pathToFile)
|
||||
.then((buf) => {
|
||||
return rp({
|
||||
url,
|
||||
method: 'PUT',
|
||||
body: buf,
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
14
packages/server/lib/cloud/upload/send_file.ts
Normal file
14
packages/server/lib/cloud/upload/send_file.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const rp = require('@cypress/request-promise')
|
||||
const { fs } = require('../../util/fs')
|
||||
|
||||
export const sendFile = (filePath: string, uploadUrl: string) => {
|
||||
return fs
|
||||
.readFileAsync(filePath)
|
||||
.then((buf) => {
|
||||
return rp({
|
||||
url: uploadUrl,
|
||||
method: 'PUT',
|
||||
body: buf,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const api = require('./api')
|
||||
const api = require('./api').default
|
||||
const cache = require('../cache')
|
||||
|
||||
import type { CachedUser } from '@packages/types'
|
||||
@@ -25,6 +25,8 @@ export = {
|
||||
if (authToken) {
|
||||
return api.postLogout(authToken)
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const la = require('lazy-ass')
|
||||
const chalk = require('chalk')
|
||||
const check = require('check-more-types')
|
||||
const debug = require('debug')('cypress:server:record')
|
||||
const debugCiInfo = require('debug')('cypress:server:record:ci-info')
|
||||
@@ -12,21 +11,19 @@ const { telemetry } = require('@packages/telemetry')
|
||||
|
||||
const { hideKeys } = require('@packages/config')
|
||||
|
||||
const api = require('../cloud/api')
|
||||
const api = require('../cloud/api').default
|
||||
const exception = require('../cloud/exception')
|
||||
const upload = require('../cloud/upload')
|
||||
|
||||
const errors = require('../errors')
|
||||
const capture = require('../capture')
|
||||
const Config = require('../config')
|
||||
const env = require('../util/env')
|
||||
const terminal = require('../util/terminal')
|
||||
const ciProvider = require('../util/ci_provider')
|
||||
const { printPendingArtifactUpload, printCompletedArtifactUpload, beginUploadActivityOutput } = require('../util/print-run')
|
||||
|
||||
const testsUtils = require('../util/tests_utils')
|
||||
const specWriter = require('../util/spec_writer')
|
||||
const { fs } = require('../util/fs')
|
||||
const { performance } = require('perf_hooks')
|
||||
|
||||
const { uploadArtifacts } = require('../cloud/artifacts/upload_artifacts')
|
||||
|
||||
// dont yell about any errors either
|
||||
const runningInternalTests = () => {
|
||||
@@ -138,365 +135,6 @@ returns:
|
||||
]
|
||||
*/
|
||||
|
||||
const uploadArtifactBatch = async (artifacts, protocolManager, quiet) => {
|
||||
const priority = {
|
||||
'video': 0,
|
||||
'screenshots': 1,
|
||||
'protocol': 2,
|
||||
}
|
||||
const labels = {
|
||||
'video': 'Video',
|
||||
'screenshots': 'Screenshot',
|
||||
'protocol': 'Test Replay',
|
||||
}
|
||||
|
||||
artifacts.sort((a, b) => {
|
||||
return priority[a.reportKey] - priority[b.reportKey]
|
||||
})
|
||||
|
||||
const preparedArtifacts = await Promise.all(artifacts.map(async (artifact) => {
|
||||
if (artifact.skip) {
|
||||
return artifact
|
||||
}
|
||||
|
||||
if (artifact.reportKey === 'protocol') {
|
||||
try {
|
||||
if (protocolManager.hasFatalError()) {
|
||||
const error = protocolManager.getFatalError().error
|
||||
|
||||
debug('protocol fatal error encountered', {
|
||||
message: error.message,
|
||||
captureMethod: error.captureMethod,
|
||||
stack: error.stack,
|
||||
})
|
||||
|
||||
return {
|
||||
...artifact,
|
||||
skip: true,
|
||||
error: error.message || 'Unknown Error',
|
||||
errorStack: error.stack || 'Unknown Stack',
|
||||
}
|
||||
}
|
||||
|
||||
const archiveInfo = await protocolManager.getArchiveInfo()
|
||||
|
||||
if (archiveInfo === undefined) {
|
||||
return {
|
||||
...artifact,
|
||||
skip: true,
|
||||
error: 'No test data recorded',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...artifact,
|
||||
...archiveInfo,
|
||||
}
|
||||
} catch (err) {
|
||||
debug('failed to prepare protocol artifact', {
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
})
|
||||
|
||||
return {
|
||||
...artifact,
|
||||
skip: true,
|
||||
error: err.message,
|
||||
errorStack: err.stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (artifact.filePath) {
|
||||
try {
|
||||
const { size } = await fs.statAsync(artifact.filePath)
|
||||
|
||||
return {
|
||||
...artifact,
|
||||
fileSize: size,
|
||||
}
|
||||
} catch (err) {
|
||||
debug('failed to get stats for upload artifact %o', {
|
||||
file: artifact.filePath,
|
||||
stack: err.stack,
|
||||
})
|
||||
|
||||
return {
|
||||
...artifact,
|
||||
skip: true,
|
||||
error: err.message,
|
||||
errorStack: err.stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return artifact
|
||||
}))
|
||||
|
||||
if (!quiet) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
|
||||
terminal.header('Uploading Cloud Artifacts', {
|
||||
color: ['blue'],
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
}
|
||||
|
||||
preparedArtifacts.forEach((artifact) => {
|
||||
debug('preparing to upload artifact %O', {
|
||||
...artifact,
|
||||
payload: typeof artifact.payload,
|
||||
})
|
||||
|
||||
if (!quiet) {
|
||||
printPendingArtifactUpload(artifact, labels)
|
||||
}
|
||||
})
|
||||
|
||||
let stopUploadActivityOutput
|
||||
|
||||
if (!quiet && preparedArtifacts.filter(({ skip }) => !skip).length) {
|
||||
stopUploadActivityOutput = beginUploadActivityOutput()
|
||||
}
|
||||
|
||||
const uploadResults = await Promise.all(
|
||||
preparedArtifacts.map(async (artifact) => {
|
||||
if (artifact.skip) {
|
||||
debug('nothing to upload for artifact %O', artifact)
|
||||
|
||||
return {
|
||||
key: artifact.reportKey,
|
||||
skipped: true,
|
||||
url: artifact.uploadUrl,
|
||||
...(artifact.error && {
|
||||
error: artifact.error,
|
||||
errorStack: artifact.errorStack,
|
||||
success: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const startTime = performance.now()
|
||||
|
||||
debug('uploading artifact %O', {
|
||||
...artifact,
|
||||
payload: typeof artifact.payload,
|
||||
})
|
||||
|
||||
try {
|
||||
if (artifact.reportKey === 'protocol') {
|
||||
const res = await protocolManager.uploadCaptureArtifact(artifact)
|
||||
|
||||
return {
|
||||
...res,
|
||||
pathToFile: 'Test Replay',
|
||||
url: artifact.uploadUrl,
|
||||
fileSize: artifact.fileSize,
|
||||
key: artifact.reportKey,
|
||||
uploadDuration: performance.now() - startTime,
|
||||
}
|
||||
}
|
||||
|
||||
const res = await upload.send(artifact.filePath, artifact.uploadUrl)
|
||||
|
||||
return {
|
||||
...res,
|
||||
success: true,
|
||||
url: artifact.uploadUrl,
|
||||
pathToFile: artifact.filePath,
|
||||
fileSize: artifact.fileSize,
|
||||
key: artifact.reportKey,
|
||||
uploadDuration: performance.now() - startTime,
|
||||
}
|
||||
} catch (err) {
|
||||
debug('failed to upload artifact %o', {
|
||||
file: artifact.filePath,
|
||||
url: artifact.uploadUrl,
|
||||
stack: err.stack,
|
||||
})
|
||||
|
||||
if (err.errors) {
|
||||
const lastError = _.last(err.errors)
|
||||
|
||||
return {
|
||||
key: artifact.reportKey,
|
||||
success: false,
|
||||
error: lastError.message,
|
||||
allErrors: err.errors,
|
||||
url: artifact.uploadUrl,
|
||||
pathToFile: artifact.filePath,
|
||||
uploadDuration: performance.now() - startTime,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: artifact.reportKey,
|
||||
success: false,
|
||||
error: err.message,
|
||||
errorStack: err.stack,
|
||||
url: artifact.uploadUrl,
|
||||
pathToFile: artifact.filePath,
|
||||
uploadDuration: performance.now() - startTime,
|
||||
}
|
||||
}
|
||||
}),
|
||||
).finally(() => {
|
||||
if (stopUploadActivityOutput) {
|
||||
stopUploadActivityOutput()
|
||||
}
|
||||
})
|
||||
|
||||
const attemptedUploadResults = uploadResults.filter(({ skipped }) => {
|
||||
return !skipped
|
||||
})
|
||||
|
||||
if (!quiet && attemptedUploadResults.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
|
||||
terminal.header('Uploaded Cloud Artifacts', {
|
||||
color: ['blue'],
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
|
||||
attemptedUploadResults.forEach(({ key, skipped, ...report }, i, { length }) => {
|
||||
printCompletedArtifactUpload({ key, ...report }, labels, chalk.grey(`${i + 1}/${length}`))
|
||||
})
|
||||
}
|
||||
|
||||
return uploadResults.reduce((acc, { key, skipped, ...report }) => {
|
||||
if (key === 'protocol') {
|
||||
let { error, errorStack, allErrors } = report
|
||||
|
||||
if (allErrors) {
|
||||
error = `Failed to upload Test Replay after ${allErrors.length} attempts. Errors: ${allErrors.map((error) => error.message).join(', ')}`
|
||||
errorStack = allErrors.map((error) => error.stack).join(', ')
|
||||
} else if (error) {
|
||||
error = `Failed to upload Test Replay: ${error}`
|
||||
}
|
||||
|
||||
return skipped && !report.error ? acc : {
|
||||
...acc,
|
||||
[key]: {
|
||||
...report,
|
||||
error,
|
||||
errorStack,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return skipped ? acc : {
|
||||
...acc,
|
||||
[key]: (key === 'screenshots') ? [...acc.screenshots, report] : report,
|
||||
}
|
||||
}, {
|
||||
video: undefined,
|
||||
screenshots: [],
|
||||
protocol: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const uploadArtifacts = async (options = {}) => {
|
||||
const { protocolManager, video, screenshots, videoUploadUrl, captureUploadUrl, protocolCaptureMeta, screenshotUploadUrls, quiet, runId, instanceId, spec, platform, projectId } = options
|
||||
|
||||
const artifacts = []
|
||||
|
||||
if (videoUploadUrl) {
|
||||
artifacts.push({
|
||||
reportKey: 'video',
|
||||
uploadUrl: videoUploadUrl,
|
||||
filePath: video,
|
||||
})
|
||||
} else {
|
||||
artifacts.push({
|
||||
reportKey: 'video',
|
||||
skip: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (screenshotUploadUrls.length) {
|
||||
screenshotUploadUrls.map(({ screenshotId, uploadUrl }) => {
|
||||
const screenshot = _.find(screenshots, { screenshotId })
|
||||
|
||||
debug('screenshot: %o', screenshot)
|
||||
|
||||
return {
|
||||
reportKey: 'screenshots',
|
||||
uploadUrl,
|
||||
filePath: screenshot.path,
|
||||
}
|
||||
}).forEach((screenshotArtifact) => {
|
||||
artifacts.push(screenshotArtifact)
|
||||
})
|
||||
} else {
|
||||
artifacts.push({
|
||||
reportKey: 'screenshots',
|
||||
skip: true,
|
||||
})
|
||||
}
|
||||
|
||||
debug('capture manifest: %O', { captureUploadUrl, protocolCaptureMeta, protocolManager })
|
||||
if (protocolManager && (captureUploadUrl || (protocolCaptureMeta && protocolCaptureMeta.url))) {
|
||||
artifacts.push({
|
||||
reportKey: 'protocol',
|
||||
uploadUrl: captureUploadUrl || protocolCaptureMeta.url,
|
||||
})
|
||||
} else if (protocolCaptureMeta && protocolCaptureMeta.disabledMessage) {
|
||||
artifacts.push({
|
||||
reportKey: 'protocol',
|
||||
message: protocolCaptureMeta.disabledMessage,
|
||||
skip: true,
|
||||
})
|
||||
}
|
||||
|
||||
let uploadReport
|
||||
|
||||
try {
|
||||
uploadReport = await uploadArtifactBatch(artifacts, protocolManager, quiet)
|
||||
} catch (err) {
|
||||
errors.warning('CLOUD_CANNOT_UPLOAD_ARTIFACTS', err)
|
||||
|
||||
return exception.create(err)
|
||||
}
|
||||
|
||||
debug('checking for protocol errors', protocolManager?.hasErrors())
|
||||
if (protocolManager) {
|
||||
try {
|
||||
await protocolManager.reportNonFatalErrors({
|
||||
specName: spec.name,
|
||||
osName: platform.osName,
|
||||
projectSlug: projectId,
|
||||
})
|
||||
} catch (err) {
|
||||
debug('Failed to send protocol errors %O', err)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
debug('upload report: %O', uploadReport)
|
||||
const res = await api.updateInstanceArtifacts({
|
||||
runId, instanceId,
|
||||
}, uploadReport)
|
||||
|
||||
return res
|
||||
} catch (err) {
|
||||
debug('failed updating artifact status %o', {
|
||||
stack: err.stack,
|
||||
})
|
||||
|
||||
errors.warning('CLOUD_CANNOT_UPLOAD_ARTIFACTS_PROTOCOL', err)
|
||||
|
||||
if (err.statusCode !== 503) {
|
||||
return exception.create(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateInstanceStdout = async (options = {}) => {
|
||||
const { runId, instanceId, captured } = options
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@ import env from './env'
|
||||
import terminal from './terminal'
|
||||
import { getIsCi } from './ci_provider'
|
||||
import * as experiments from '../experiments'
|
||||
import type { SpecFile } from '@packages/types'
|
||||
import type { SpecFile, ProtocolError } from '@packages/types'
|
||||
import type { Cfg } from '../project-base'
|
||||
import type { Browser } from '../browsers/types'
|
||||
import type { Table } from 'cli-table3'
|
||||
import type { CypressRunResult } from '../modes/results'
|
||||
import type { IArtifact, ArtifactUploadResult } from '../cloud/artifacts/artifact'
|
||||
|
||||
type Screenshot = {
|
||||
width: number
|
||||
@@ -572,30 +573,9 @@ const formatFileSize = (bytes: number) => {
|
||||
return prettyBytes(bytes)
|
||||
}
|
||||
|
||||
type ArtifactLike = {
|
||||
reportKey: 'protocol' | 'screenshots' | 'video'
|
||||
filePath?: string
|
||||
fileSize?: number | BigInt
|
||||
message?: string
|
||||
skip?: boolean
|
||||
error: string
|
||||
}
|
||||
|
||||
export const printPendingArtifactUpload = <T extends ArtifactLike> (artifact: T, labels: Record<'protocol' | 'screenshots' | 'video', string>): void => {
|
||||
export const printPendingArtifactUpload = (artifact: IArtifact, labels: Record<'protocol' | 'screenshots' | 'video', string>): void => {
|
||||
process.stdout.write(` - ${labels[artifact.reportKey]} `)
|
||||
|
||||
if (artifact.skip) {
|
||||
if (artifact.reportKey === 'protocol' && artifact.error) {
|
||||
process.stdout.write(`- Failed Capturing - ${artifact.error}`)
|
||||
} else {
|
||||
process.stdout.write('- Nothing to upload ')
|
||||
}
|
||||
}
|
||||
|
||||
if (artifact.reportKey === 'protocol' && artifact.message) {
|
||||
process.stdout.write(`- ${artifact.message}`)
|
||||
}
|
||||
|
||||
if (artifact.fileSize) {
|
||||
process.stdout.write(`- ${formatFileSize(Number(artifact.fileSize))}`)
|
||||
}
|
||||
@@ -607,25 +587,69 @@ export const printPendingArtifactUpload = <T extends ArtifactLike> (artifact: T,
|
||||
process.stdout.write('\n')
|
||||
}
|
||||
|
||||
type ArtifactUploadResultLike = {
|
||||
pathToFile?: string
|
||||
key: string
|
||||
fileSize?: number | BigInt
|
||||
success: boolean
|
||||
error?: string
|
||||
skipped?: boolean
|
||||
uploadDuration?: number
|
||||
export const printSkippedArtifact = (label: string, message: string = 'Nothing to upload', error?: string) => {
|
||||
process.stdout.write(` - ${label} - ${message} `)
|
||||
if (error) {
|
||||
process.stdout.write(`- ${error}`)
|
||||
}
|
||||
|
||||
process.stdout.write('\n')
|
||||
}
|
||||
|
||||
export const printCompletedArtifactUpload = <T extends ArtifactUploadResultLike> (artifactUploadResult: T, labels: Record<'protocol' | 'screenshots' | 'video', string>, num: string): void => {
|
||||
const { pathToFile, key, fileSize, success, error, skipped, uploadDuration } = artifactUploadResult
|
||||
export const logUploadManifest = (artifacts: IArtifact[], protocolCaptureMeta: {
|
||||
url?: string
|
||||
disabledMessage?: string
|
||||
}, protocolFatalError?: ProtocolError) => {
|
||||
const labels = {
|
||||
'video': 'Video',
|
||||
'screenshots': 'Screenshot',
|
||||
'protocol': 'Test Replay',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
terminal.header('Uploading Cloud Artifacts', {
|
||||
color: ['blue'],
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
|
||||
const video = artifacts.find(({ reportKey }) => reportKey === 'video')
|
||||
const screenshots = artifacts.filter(({ reportKey }) => reportKey === 'screenshots')
|
||||
const protocol = artifacts.find(({ reportKey }) => reportKey === 'protocol')
|
||||
|
||||
if (video) {
|
||||
printPendingArtifactUpload(video, labels)
|
||||
} else {
|
||||
printSkippedArtifact('Video')
|
||||
}
|
||||
|
||||
if (screenshots.length) {
|
||||
screenshots.forEach(((screenshot) => {
|
||||
printPendingArtifactUpload(screenshot, labels)
|
||||
}))
|
||||
} else {
|
||||
printSkippedArtifact('Screenshot')
|
||||
}
|
||||
|
||||
// if protocolFatalError exists here, there is not a protocol artifact to attempt to upload
|
||||
if (protocolFatalError) {
|
||||
printSkippedArtifact('Test Replay', 'Failed Capturing', protocolFatalError.error.message)
|
||||
} else if (protocol) {
|
||||
if (!protocolFatalError) {
|
||||
printPendingArtifactUpload(protocol, labels)
|
||||
}
|
||||
} else if (protocolCaptureMeta.disabledMessage) {
|
||||
printSkippedArtifact('Test Replay', 'Nothing to upload', protocolCaptureMeta.disabledMessage)
|
||||
}
|
||||
}
|
||||
|
||||
export const printCompletedArtifactUpload = ({ pathToFile, key, fileSize, success, error, uploadDuration }: ArtifactUploadResult, labels: Record<'protocol' | 'screenshots' | 'video', string>, num: string): void => {
|
||||
process.stdout.write(` - ${labels[key]} `)
|
||||
|
||||
if (success) {
|
||||
process.stdout.write(`- Done Uploading ${formatFileSize(Number(fileSize))}`)
|
||||
} else if (skipped) {
|
||||
process.stdout.write(`- Nothing to Upload`)
|
||||
} else {
|
||||
process.stdout.write(`- Failed Uploading`)
|
||||
}
|
||||
@@ -649,6 +673,40 @@ export const printCompletedArtifactUpload = <T extends ArtifactUploadResultLike>
|
||||
process.stdout.write('\n')
|
||||
}
|
||||
|
||||
export const logUploadResults = (results: ArtifactUploadResult[], protocolFatalError: ProtocolError | undefined) => {
|
||||
const labels = {
|
||||
'video': 'Video',
|
||||
'screenshots': 'Screenshot',
|
||||
'protocol': 'Test Replay',
|
||||
}
|
||||
|
||||
// if protocol did not attempt an upload due to a fatal error, there will still be an upload result - this is
|
||||
// so we can report the failure properly to instance/artifacts. But, we do not want to display it here.
|
||||
const trimmedResults = protocolFatalError && protocolFatalError.captureMethod !== 'uploadCaptureArtifact' ?
|
||||
results.filter(((result) => {
|
||||
return result.key !== 'protocol'
|
||||
})) :
|
||||
results
|
||||
|
||||
if (!trimmedResults.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
|
||||
terminal.header('Uploaded Cloud Artifacts', {
|
||||
color: ['blue'],
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('')
|
||||
|
||||
trimmedResults.forEach(({ key, ...report }, i, { length }) => {
|
||||
printCompletedArtifactUpload({ key, ...report }, labels, chalk.grey(`${i + 1}/${length}`))
|
||||
})
|
||||
}
|
||||
|
||||
const UPLOAD_ACTIVITY_INTERVAL = typeof env.get('CYPRESS_UPLOAD_ACTIVITY_INTERVAL') === 'undefined' ? 15000 : env.get('CYPRESS_UPLOAD_ACTIVITY_INTERVAL')
|
||||
|
||||
export const beginUploadActivityOutput = () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ const ciProvider = require(`../../lib/util/ci_provider`)
|
||||
const settings = require(`../../lib/util/settings`)
|
||||
const Windows = require(`../../lib/gui/windows`)
|
||||
const interactiveMode = require(`../../lib/modes/interactive`)
|
||||
const api = require(`../../lib/cloud/api`)
|
||||
const api = require(`../../lib/cloud/api`).default
|
||||
const cwd = require(`../../lib/cwd`)
|
||||
const user = require(`../../lib/cloud/user`)
|
||||
const cache = require(`../../lib/cache`)
|
||||
|
||||
@@ -13,7 +13,7 @@ const {
|
||||
agent,
|
||||
} = require('@packages/network')
|
||||
const pkg = require('@packages/root')
|
||||
const api = require('../../../../lib/cloud/api')
|
||||
const api = require('../../../../lib/cloud/api').default
|
||||
const cache = require('../../../../lib/cache')
|
||||
const errors = require('../../../../lib/errors')
|
||||
const machineId = require('../../../../lib/cloud/machine_id')
|
||||
@@ -237,7 +237,7 @@ describe('lib/cloud/api', () => {
|
||||
|
||||
if (!prodApi) {
|
||||
prodApi = stealthyRequire(require.cache, () => {
|
||||
return require('../../../../lib/cloud/api')
|
||||
return require('../../../../lib/cloud/api').default
|
||||
}, () => {
|
||||
require('../../../../lib/cloud/encryption')
|
||||
}, module)
|
||||
|
||||
@@ -2,7 +2,7 @@ require('../../spec_helper')
|
||||
|
||||
delete global.fs
|
||||
|
||||
const api = require('../../../lib/cloud/api')
|
||||
const api = require('../../../lib/cloud/api').default
|
||||
const user = require('../../../lib/cloud/user')
|
||||
const exception = require('../../../lib/cloud/exception')
|
||||
const system = require('../../../lib/util/system')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require('../../spec_helper')
|
||||
|
||||
const api = require('../../../lib/cloud/api')
|
||||
const api = require('../../../lib/cloud/api').default
|
||||
const cache = require('../../../lib/cache')
|
||||
const user = require('../../../lib/cloud/user')
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const commitInfo = require('@cypress/commit-info')
|
||||
const mockedEnv = require('mocked-env')
|
||||
|
||||
const errors = require(`../../../lib/errors`)
|
||||
const api = require(`../../../lib/cloud/api`)
|
||||
const api = require(`../../../lib/cloud/api`).default
|
||||
const exception = require(`../../../lib/cloud/exception`)
|
||||
const recordMode = require(`../../../lib/modes/record`)
|
||||
const ciProvider = require(`../../../lib/util/ci_provider`)
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface ProtocolError {
|
||||
captureMethod: ProtocolCaptureMethod
|
||||
fatal?: boolean
|
||||
runnableId?: string
|
||||
isUploadError?: boolean
|
||||
}
|
||||
|
||||
type ProtocolErrorReportEntry = Omit<ProtocolError, 'fatal' | 'error'> & {
|
||||
@@ -74,7 +75,7 @@ export type ProtocolErrorReport = {
|
||||
|
||||
export type CaptureArtifact = {
|
||||
uploadUrl: string
|
||||
fileSize: number
|
||||
fileSize: number | bigint
|
||||
filePath: string
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ export interface ProtocolManagerShape extends AppCaptureProtocolCommon {
|
||||
setupProtocol(script: string, options: ProtocolManagerOptions): Promise<void>
|
||||
beforeSpec (spec: { instanceId: string }): void
|
||||
reportNonFatalErrors (clientMetadata: any): Promise<void>
|
||||
uploadCaptureArtifact(artifact: CaptureArtifact, timeout?: number): Promise<{ fileSize: number, success: boolean, error?: string } | void>
|
||||
uploadCaptureArtifact(artifact: CaptureArtifact, timeout?: number): Promise<{ fileSize: number | bigint, success: boolean, error?: string } | void>
|
||||
}
|
||||
|
||||
type Response = {
|
||||
|
||||
Reference in New Issue
Block a user