Skip to content

Commit

Permalink
feat: protocol terminal output (#27402)
Browse files Browse the repository at this point in the history
* uploading initiation & legit no upload messaging

* report errors

* makes upload reporting uniform across artifact types

* retrieve capture meta from correct cloud endpoint

* moves skipped artifact insertion to more reasonable point

* rm unneccessary paren around Promise.all on upload

* improve zipped filesize determination for protocol uploads, clean up get db signature in protocol manager

* changelog

* add url onto protocol failure report

* rm unused err.cause consts

* ensure artifact PUT server mock resolves in system tests

* extract terminal output logic from upload flow, mask filepaths and filesizes in system tests

* update return shape for postRun when test replay is enabled

* pad beginning of liine for upload reports

* update upload messaging snapshots for record spec

* improve trailing whitespace for artifact upload terminal output

* since we are now waiting for artifact report, must include urls in test assertion

* respect quiet mode

* address correct index of reqs for api reordering specs test

* updates snapshots & adds missing artifacts PUT req for api skips specs not in parallel

* updates tests for skipping specs in parallel

* update snapshot for no upload when video disabled test

* update snapshot for update instance 500

* updates snapshot for postInstanceTests 500

* update instance stdout 500 snapshot update

* improve message format when error on uploading, update snapshots

* snapshot for api retry on error

* update snapshot for sendPreflight system tests

* update snapshots for api interaction private tests limit warning

* update snapshots when over tests limit

* updates snapshots for protocol retrieval, bypass stub verification in test mode

* set gzip header on stubbed capture code server endpoint so client can verify

* accept BROWSER env var to reduce screenshot dimension difference from local to ci

* adds artifacts PUT to manifest for stdout 500 system test

* fixes snapshot browser workaround; updates url manifest for record system tests

* fix whitespace between filesize and path in upload manifest

* manually update snapshots for video_compression

* adds system tests for disabled message from server, file size exceeded

* additional tests, bugfixes

* add logging to determine source of ci error with db files

* ensure protocol tmp dir is created before tests

* rm test env force return of failed sig check on protocol runtime

* code review comments

* fix priority nums on artifact readout

* rm commented code from protocol stub
  • Loading branch information
cacieprins committed Aug 10, 2023
1 parent d27e249 commit fff7490
Show file tree
Hide file tree
Showing 16 changed files with 1,453 additions and 270 deletions.
5 changes: 5 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ _Released 08/1/2023 (PENDING)_
- The [`videoUploadOnPasses`](https://docs.cypress.io/guides/references/configuration#Videos) configuration option has been removed. Please see our [screenshots & videos guide](https://docs.cypress.io/guides/guides/screenshots-and-videos#Delete-videos-for-specs-without-failing-or-retried-tests) on how to accomplish similar functionality. Addresses [#26899](https://github.com/cypress-io/cypress/issues/26899).
- The deprecated configuration option, `nodeVersion` has been removed. Addresses [#27016](https://github.com/cypress-io/cypress/issues/27016).
- The properties and values returned by the [Module API](https://docs.cypress.io/guides/guides/module-api) and included in the arguments of handlers for the [`after:run`](https://docs.cypress.io/api/plugins/after-run-api) and [`after:spec`](https://docs.cypress.io/api/plugins/after-spec-api) have been changed to be more consistent. Addresses [#23805](https://github.com/cypress-io/cypress/issues/23805).

**Features:**

- Consolidates and improves terminal output when uploading test artifacts to Cypress Cloud. Addressed in [#27402](https://github.com/cypress-io/cypress/pull/27402)

## 12.17.4

_Released 08/15/2023 (PENDING)_
Expand Down
24 changes: 20 additions & 4 deletions packages/server/lib/cloud/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ type CreateRunResponse = {
name: string
})[]
captureProtocolUrl?: string | undefined
capture?: {
url?: string
tags: string[] | null
} | undefined
}

type UpdateInstanceArtifactsOptions = {
Expand Down Expand Up @@ -382,9 +386,12 @@ module.exports = {
.then(async (result: CreateRunResponse) => {
let protocolManager = new ProtocolManager()

const captureProtocolUrl = result.capture?.url || result.captureProtocolUrl

debugProtocol({ captureProtocolUrl })
try {
if (result.captureProtocolUrl || process.env.CYPRESS_LOCAL_PROTOCOL_PATH) {
const script = await this.getCaptureProtocolScript(result.captureProtocolUrl || process.env.CYPRESS_LOCAL_PROTOCOL_PATH)
if (captureProtocolUrl || process.env.CYPRESS_LOCAL_PROTOCOL_PATH) {
const script = await this.getCaptureProtocolScript(captureProtocolUrl || process.env.CYPRESS_LOCAL_PROTOCOL_PATH)

if (script) {
options.project.protocolManager = protocolManager
Expand Down Expand Up @@ -633,6 +640,17 @@ module.exports = {
return fs.promises.readFile(process.env.CYPRESS_LOCAL_PROTOCOL_PATH, 'utf8')
}

debugProtocol({
url,
headers: {
'x-route-version': '1',
'x-cypress-signature': PUBLIC_KEY_VERSION,
},
agent,
encrypt: 'signed',
resolveWithFullResponse: true,
})

return retryWithBackoff(async (attemptIndex) => {
return rp.get({
url,
Expand All @@ -649,8 +667,6 @@ module.exports = {
const verified = enc.verifySignature(res.body, res.headers['x-cypress-signature'])

if (!verified) {
debugProtocol(`Unable to verify protocol signature %s`, url)

return null
}

Expand Down
96 changes: 59 additions & 37 deletions packages/server/lib/cloud/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import fs from 'fs-extra'
import Debug from 'debug'
import type { ProtocolManagerShape, AppCaptureProtocolInterface, CDPClient, ProtocolError } from '@packages/types'
import type { ProtocolManagerShape, AppCaptureProtocolInterface, CDPClient, ProtocolError, CaptureArtifact } from '@packages/types'
import Database from 'better-sqlite3'
import path from 'path'
import os from 'os'
import { createGzip } from 'zlib'
import fetch from 'cross-fetch'
import Module from 'module'
import env from '../util/env'

const routes = require('./routes')
const pkg = require('@packages/root')
Expand All @@ -20,6 +21,13 @@ const DELETE_DB = !process.env.CYPRESS_LOCAL_PROTOCOL_PATH
// Timeout for upload
const TWO_MINUTES = 120000

const DB_SIZE_LIMIT = 5000000000

const dbSizeLimit = () => {
return env.get('CYPRESS_INTERNAL_SYSTEM_TESTS') === '1' ?
200 : DB_SIZE_LIMIT
}

/**
* requireScript, does just that, requires the passed in script as if it was a module.
* @param script - string
Expand Down Expand Up @@ -64,6 +72,7 @@ export class ProtocolManager implements ProtocolManagerShape {
this._protocol = new AppCaptureProtocol()
}
} catch (error) {
debug(error)
if (CAPTURE_ERRORS) {
this._errors.push({
error,
Expand Down Expand Up @@ -173,39 +182,53 @@ export class ProtocolManager implements ProtocolManagerShape {
this.invokeSync('resetTest', testId)
}

async uploadCaptureArtifact ({ uploadUrl, timeout }) {
const dbPath = this._dbPath
canUpload (): boolean {
return !!this._protocol && !!this._dbPath && !!this._db
}

if (!this._protocol || !dbPath || !this._db) {
if (this._errors.length) {
await this.sendErrors()
}
hasErrors (): boolean {
return !!this._errors.length
}

async getZippedDb (): Promise<Buffer | void> {
const dbPath = this._dbPath

debug('reading db from', dbPath)
if (!dbPath) {
return
}

debug(`uploading %s to %s`, dbPath, uploadUrl)
return new Promise((resolve, reject) => {
const gzip = createGzip()
const buffers: Buffer[] = []

let zippedFileSize = 0
gzip.on('data', (args) => {
buffers.push(args)
})

try {
const body = await new Promise((resolve, reject) => {
const gzip = createGzip()
const buffers: Buffer[] = []
gzip.on('end', () => {
resolve(Buffer.concat(buffers))
})

gzip.on('data', (args) => {
zippedFileSize += args.length
buffers.push(args)
})
gzip.on('error', reject)

gzip.on('end', () => {
resolve(Buffer.concat(buffers))
})
fs.createReadStream(dbPath).pipe(gzip, { end: true })
})
}

gzip.on('error', reject)
async uploadCaptureArtifact ({ uploadUrl, payload, fileSize }: CaptureArtifact, timeout) {
const dbPath = this._dbPath

fs.createReadStream(dbPath).pipe(gzip, { end: true })
})
if (!this._protocol || !dbPath || !this._db) {
return
}

debug(`uploading %s to %s`, dbPath, uploadUrl, fileSize)

try {
if (fileSize > dbSizeLimit()) {
throw new Error(`Spec recording too large: db is ${fileSize} bytes, limit is ${dbSizeLimit()} bytes`)
}

const controller = new AbortController()

Expand All @@ -214,30 +237,30 @@ export class ProtocolManager implements ProtocolManagerShape {
}, timeout ?? TWO_MINUTES)

const res = await fetch(uploadUrl, {
// @ts-expect-error - this is supported
agent,
method: 'PUT',
// @ts-expect-error - this is supported
body,
body: payload,
headers: {
'Content-Encoding': 'gzip',
'Content-Type': 'binary/octet-stream',
'Content-Length': `${zippedFileSize}`,
'Content-Length': `${fileSize}`,
},
signal: controller.signal,
})

if (res.ok) {
return {
fileSize: zippedFileSize,
fileSize,
success: true,
}
}

const err = await res.text()
const errorMessage = await res.text()

debug(`error response text: %s`, err)
debug(`error response text: %s`, errorMessage)

throw new Error(err)
throw new Error(errorMessage)
} catch (e) {
if (CAPTURE_ERRORS) {
this._errors.push({
Expand All @@ -248,19 +271,16 @@ export class ProtocolManager implements ProtocolManagerShape {

throw e
} finally {
await Promise.all([
this.sendErrors(),
await (
DELETE_DB ? fs.unlink(dbPath).catch((e) => {
debug(`Error unlinking db %o`, e)
}) : Promise.resolve(),
])

// Reset errors after they have been sent
this._errors = []
}) : Promise.resolve()
)
}
}

async sendErrors (protocolErrors: ProtocolError[] = this._errors) {
debug('invoke: sendErrors for protocol %O', protocolErrors)
if (protocolErrors.length === 0) {
return
}
Expand Down Expand Up @@ -295,6 +315,8 @@ export class ProtocolManager implements ProtocolManagerShape {
} catch (e) {
debug(`Error calling ProtocolManager.sendErrors: %o, original errors %o`, e, protocolErrors)
}

this._errors = []
}

/**
Expand Down
Loading

4 comments on commit fff7490

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on fff7490 Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.0.0/linux-x64/release/13.0.0-fff74900fb90c0a06cce85ee3ab8766fe22af36f/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on fff7490 Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.0.0/darwin-arm64/release/13.0.0-fff74900fb90c0a06cce85ee3ab8766fe22af36f/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on fff7490 Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.0.0/linux-arm64/release/13.0.0-fff74900fb90c0a06cce85ee3ab8766fe22af36f/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on fff7490 Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.0.0/darwin-x64/release/13.0.0-fff74900fb90c0a06cce85ee3ab8766fe22af36f/cypress.tgz

Please sign in to comment.