Skip to content

Commit

Permalink
fix: provide feedback for old operating systems
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanthemanuel committed Sep 26, 2023
1 parent 2247ffd commit 9f31c58
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 8 deletions.
39 changes: 39 additions & 0 deletions packages/errors/__snapshot-html__/OLD_GLIBC_VERSION.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/errors/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,12 @@ export const AllCypressErrors = {
If you're experiencing problems, downgrade dependencies and restart Cypress.
`
},

OLD_GLIBC_VERSION: () => {
return errTemplate`
You are running on a system with out of date dependencies. Test replay cannot run on this system. Please see: https://on.cypress.io/out-of-date-glibc-test-replay
`
},
} as const

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
6 changes: 6 additions & 0 deletions packages/errors/test/unit/visualSnapshotErrors_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1307,5 +1307,11 @@ describe('visual error templates', () => {
default: [],
}
},

OLD_GLIBC_VERSION: () => {
return {
default: [],
}
},
})
})
1 change: 1 addition & 0 deletions packages/graphql/schemas/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,7 @@ enum ErrorTypeEnum {
NO_PROJECT_FOUND_AT_PROJECT_ROOT
NO_PROJECT_ID
NO_SPECS_FOUND
OLD_GLIBC_VERSION
PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS
PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN
PLAN_EXCEEDS_MONTHLY_TESTS
Expand Down
37 changes: 30 additions & 7 deletions packages/server/lib/cloud/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { agent } from '@packages/network'
import pkg from '@packages/root'

import env from '../util/env'
import * as errors from '@packages/errors'

import type { Readable } from 'stream'
import type { ProtocolManagerShape, AppCaptureProtocolInterface, CDPClient, ProtocolError, CaptureArtifact, ProtocolErrorReport, ProtocolCaptureMethod, ProtocolManagerOptions, ResponseStreamOptions, ResponseEndedWithEmptyBodyOptions } from '@packages/types'

Expand Down Expand Up @@ -159,14 +161,35 @@ export class ProtocolManager implements ProtocolManagerShape {

debug('connecting to database at %s', dbPath)

const db = Database(dbPath, {
nativeBinding: path.join(require.resolve('better-sqlite3/build/Release/better_sqlite3.node')),
verbose: debugVerbose,
})
try {
const db = Database(dbPath, {
nativeBinding: path.join(require.resolve('better-sqlite3/build/Release/better_sqlite3.node')),
verbose: debugVerbose,
})

this._db = db
this._archivePath = archivePath

if (this._protocol) {
this._protocol.beforeSpec({ workingDirectory: cypressProtocolDirectory, archivePath, dbPath, db })
}
} catch (error) {
let updatedError = error

this._db = db
this._archivePath = archivePath
this.invokeSync('beforeSpec', { isEssential: true }, { workingDirectory: cypressProtocolDirectory, archivePath, dbPath, db })
if (error.message.includes('GLIBC')) {
updatedError = errors.get('OLD_GLIBC_VERSION')
}

if (CAPTURE_ERRORS) {
this._errors.push({ captureMethod: 'beforeSpec', fatal: true, error: updatedError, args: spec, runnableId: this._runnableId })

this._protocol = undefined

return
}

throw updatedError
}
}

async afterSpec () {
Expand Down
82 changes: 82 additions & 0 deletions system-tests/__snapshots__/record_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3631,3 +3631,85 @@ exports['e2e record capture-protocol enabled protocol runtime errors error in pr
`

exports['e2e record capture-protocol enabled protocol runtime errors error with better sqlite3 displays the error and reports the fatal error to the cloud via artifacts 1'] = `
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 1 found (record_pass.cy.js) │
│ Searched: cypress/e2e/record_pass* │
│ Params: Tag: false, Group: false, Parallel: false │
│ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: record_pass.cy.js (1 of 1)
Estimated: X second(s)
record pass
✓ passes
- is pending
1 passing
1 pending
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 2 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 1 │
│ Skipped: 0 │
│ Screenshots: 1 │
│ Video: false │
│ Duration: X seconds │
│ Estimated: X second(s) │
│ Spec Ran: record_pass.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png (400x1022)
(Uploading Cloud Artifacts)
- Video - Nothing to upload
- Screenshot - 1 kB /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png
- Test Replay - Failed Capturing - You are running on a system with out of date dependencies. Test replay cannot run on this system. Please see: https://on.cypress.io/out-of-date-glibc-test-replay
(Uploaded Cloud Artifacts)
- Screenshot - Done Uploading 1 kB 1/1 /XXX/XXX/XXX/cypress/screenshots/record_pass.cy.js/yay it passes.png
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ record_pass.cy.js XX:XX 2 1 - 1 - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 2 1 - 1 -
───────────────────────────────────────────────────────────────────────────────────────────────────────
Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12
`
2 changes: 2 additions & 0 deletions system-tests/lib/protocol-stubs/protocolStubResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ export const PROTOCOL_STUB_BEFORESPEC_ERROR = stub('protocolStubWithBeforeSpecEr
export const PROTOCOL_STUB_NONFATAL_ERROR = stub('protocolStubWithNonFatalError.ts')

export const PROTOCOL_STUB_BEFORETEST_ERROR = stub('protocolStubWithBeforeTestError.ts')

export const PROTOCOL_STUB_WITH_DATABASE_ERROR = stub('protocolStubWithDatabaseError.ts')
127 changes: 127 additions & 0 deletions system-tests/lib/protocol-stubs/protocolStubWithDatabaseError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import fs from 'fs-extra'
import type { AppCaptureProtocolInterface, ResponseEndedWithEmptyBodyOptions, ResponseStreamOptions } from '@packages/types'
import type { Readable } from 'stream'

export class AppCaptureProtocol implements AppCaptureProtocolInterface {
private filename: string
private events = {
beforeSpec: [],
afterSpec: [],
beforeTest: [],
preAfterTest: [],
afterTest: [],
addRunnables: [],
connectToBrowser: [],
commandLogAdded: [],
commandLogChanged: [],
viewportChanged: [],
urlChanged: [],
pageLoading: [],
resetTest: [],
}

getDbMetadata (): { offset: number, size: number } {
return {
offset: 0,
size: 0,
}
}

responseStreamReceived (options: ResponseStreamOptions): Readable {
return options.responseStream
}

resetEvents () {
this.events.beforeTest = []
this.events.preAfterTest = []
this.events.afterTest = []
this.events.commandLogAdded = []
this.events.commandLogChanged = []
this.events.viewportChanged = []
this.events.urlChanged = []
this.events.pageLoading = []
}

connectToBrowser = (cdpClient) => {
if (cdpClient) this.events.connectToBrowser.push(true)

return Promise.resolve()
}

addRunnables = (runnables) => {
this.events.addRunnables.push(runnables)

return Promise.resolve()
}

beforeSpec = ({ archivePath, db }) => {
throw new Error(`/lib/x86_64-linux-gnu/libm.so.6: version 'GLIBC_2.29' not found (required by /home/semaphore/.cache/Cypress/13.2.0/Cypress/resources/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node)`)
}

afterSpec = () => {
this.events.afterSpec.push(true)

// since the order of the logs can vary per run, we sort them by id to ensure the snapshot can be compared
this.events.commandLogChanged.sort((log1, log2) => {
return log1.id.localeCompare(log2.id)
})

try {
fs.outputFileSync(this.filename, JSON.stringify(this.events, null, 2))
} catch (e) {
console.log('error writing protocol events', e)
}

return Promise.resolve()
}

beforeTest = (test) => {
this.events.beforeTest.push(test)

return Promise.resolve()
}

commandLogAdded = (log) => {
this.events.commandLogAdded.push(log)
}

commandLogChanged = (log) => {
// since the number of log changes can vary per run, we only want to record
// the passed/failed ones to ensure the snapshot can be compared
if (log.state === 'passed' || log.state === 'failed') {
this.events.commandLogChanged.push(log)
}
}

viewportChanged = (input) => {
this.events.viewportChanged.push(input)
}

urlChanged = (input) => {
this.events.urlChanged.push(input)
}

pageLoading = (input) => {
this.events.pageLoading.push(input)
}

preAfterTest = (test, options) => {
this.events.preAfterTest.push({ test, options })

return Promise.resolve()
}

afterTest = (test) => {
this.events.afterTest.push(test)

return Promise.resolve()
}

resetTest (testId: string): void {
this.resetEvents()

this.events.resetTest.push(testId)
}

responseEndedWithEmptyBody: (options: ResponseEndedWithEmptyBodyOptions) => {}
}
27 changes: 26 additions & 1 deletion system-tests/test/record_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const {
} = require('../lib/serverStub')
const { expectRunsToHaveCorrectTimings } = require('../lib/resultsUtils')
const { randomBytes } = require('crypto')
const { PROTOCOL_STUB_CONSTRUCTOR_ERROR, PROTOCOL_STUB_NONFATAL_ERROR, PROTOCOL_STUB_BEFORESPEC_ERROR, PROTOCOL_STUB_BEFORETEST_ERROR } = require('../lib/protocol-stubs/protocolStubResponse')
const { PROTOCOL_STUB_CONSTRUCTOR_ERROR, PROTOCOL_STUB_NONFATAL_ERROR, PROTOCOL_STUB_BEFORESPEC_ERROR, PROTOCOL_STUB_BEFORETEST_ERROR, PROTOCOL_STUB_WITH_DATABASE_ERROR } = require('../lib/protocol-stubs/protocolStubResponse')
const debug = require('debug')('cypress:system-tests:record_spec')
const e2ePath = Fixtures.projectPath('e2e')
const outputPath = path.join(e2ePath, 'output.json')
Expand Down Expand Up @@ -2394,6 +2394,31 @@ describe('e2e record', () => {
})
})

describe('error with better sqlite3', () => {
enableCaptureProtocol(PROTOCOL_STUB_WITH_DATABASE_ERROR)

it('displays the error and reports the fatal error to the cloud via artifacts', function () {
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
record: true,
snapshot: true,
}).then(() => {
const urls = getRequestUrls()

expect(urls).to.include.members([`PUT /instances/${instanceId}/artifacts`])
expect(urls).not.to.include.members([`PUT ${CAPTURE_PROTOCOL_UPLOAD_URL}`])

const artifactReport = getRequests().find(({ url }) => url === `PUT /instances/${instanceId}/artifacts`)?.body

expect(artifactReport?.protocol).to.exist()
expect(artifactReport?.protocol?.error).to.exist().and.not.to.be.empty()
expect(artifactReport?.protocol?.url).to.exist().and.not.be.empty()
})
})
})

describe('error in protocol beforeTest', () => {
enableCaptureProtocol(PROTOCOL_STUB_BEFORETEST_ERROR)

Expand Down

0 comments on commit 9f31c58

Please sign in to comment.