Files
cypress/packages/server/lib/plugins/preprocessor.js
Matt Henkes 62f58e00ec chore: Add open telemetry to cypress to allow us to monitor the performance of the app overtime (#26305)
* initial commit, a kinda working prototype

* Ready to test in CI

* "SyntaxError: Cannot use import statement outside a module" I blame VS code for always inserting the wrong dependency

* try using built js instead of the ts file

* typescript fixes?

* get version correctly, don't use optional chaining in child process.

* trying this, idk

* try running telemetry for driver-integration-tests-chrome

* fix missing spans, add more attributes for some spans

* fix missing spans and add suite spans

* types

* Remove un-used require

* remove spans for describe blocks in favor of the full title for tests

* migrate to sync resource discovery, start new custom exporters for spans

* encrypted

* localhost

* don't do things on child process

* latest changes

* update server start span time / add v8 snapshot span & update command span names

* prepare for sync

* don't send blank key

* make telemetry work again for sending directly to honeycomb

* web-socket exporter

* Add in IPC exporter and message the child process before disconnecting

* Use the cloud api by default

* move cloud span exporter into telemetry package

* shutdown fixes

* fix enabled

* improve types

* run in ci

* yml is the worst

* type!

* add spans for timing insights for visible areas of improvement

* type errors

* lets try sending data to staging

* types

* types again

* remove problematic attributes

* clean up exporters

* i like this better even though it doesn't seem to matter much

* some self review cleanup

* Update comment

* add debug messages

* mocha tests

* actually exit with the right code... oops

* simple mistake... have to look into how to fix with ts...

* try this i guess

* don't return undefined

* read me diagram

* color?

* no rect

* moar diagram

* docs!

* formatting

* build more binaries

* Supposedly fix cypress in cypress test failures

* test 'fixes'

* try this instead

* do not transpile cypress packages dir

* lets try escaping our regex string

* Add some diagnostics to help test the built binary....

* try a more complex solution

* fix tests probably

* just ignore the specific file

* fix unit tests

* cr updates

* Apply suggestions from code review

Co-authored-by: Matt Schile <mschile@cypress.io>
Co-authored-by: Bill Glesias <bglesias@gmail.com>

* Pr updates

* don't change the command queue

* move encoding and decoding telemetry context for ipc to the telemetry package

* build darn it

* plead for mercy from the testing gods, i merely wished to have named test reports, but clearly i have overreached.

* pr updates, send record key

* pr review

* optional chaining fails tests

* Apply suggestions from code review

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* tests for cloud-span-exporter

* bad merge

* adding tests for the remaining exporters

* note

* docs

* Correctly set test under the current run span for component testing

* gate sending the message.

* pr updates

* finally, fingers crossed.

---------

Co-authored-by: Emily Rohrbough <emilyrohrbough@yahoo.com>
Co-authored-by: Matt Schile <mschile@cypress.io>
Co-authored-by: Bill Glesias <bglesias@gmail.com>
2023-04-08 21:18:02 -05:00

150 lines
3.4 KiB
JavaScript

require('../cwd')
const _ = require('lodash')
const EE = require('events')
const path = require('path')
const debug = require('debug')('cypress:server:preprocessor')
const Promise = require('bluebird')
const appData = require('../util/app_data')
const plugins = require('../plugins')
const { telemetry } = require('@packages/telemetry')
const errorMessage = function (err = {}) {
return err.stack || err.annotated || err.message || err.toString()
}
const clientSideError = function (err) {
// eslint-disable-next-line no-console
console.log(err.message)
err = errorMessage(err)
return `\
(function () {
Cypress.action("spec:script:error", {
type: "BUNDLE_ERROR",
error: ${JSON.stringify(err)}
})
}())\
`
}
const baseEmitter = new EE()
let fileObjects = {}
let fileProcessors = {}
plugins.registerHandler((ipc) => {
ipc.on('preprocessor:rerun', (filePath) => {
debug('ipc preprocessor:rerun event')
baseEmitter.emit('file:updated', filePath)
})
baseEmitter.on('close', (filePath) => {
debug('base emitter plugin close event')
ipc.send('preprocessor:close', filePath)
})
})
// for simpler stubbing from unit tests
const API = {
errorMessage,
clientSideError,
emitter: baseEmitter,
getFile (filePath, config) {
let fileObject; let fileProcessor
debug(`getting file ${filePath}`)
filePath = path.resolve(config.projectRoot, filePath)
debug(`getFile ${filePath}`)
if (!(fileObject = fileObjects[filePath])) {
// we should be watching the file if we are NOT
// in a text terminal aka cypress run
// TODO: rename this to config.isRunMode
// vs config.isInterativeMode
const shouldWatch = !config.isTextTerminal || Boolean(process.env.CYPRESS_INTERNAL_FORCE_FILEWATCH)
const baseFilePath = filePath.replace(config.projectRoot, '')
fileObject = (fileObjects[filePath] = _.extend(new EE(), {
filePath,
shouldWatch,
outputPath: appData.getBundledFilePath(config.projectRoot, baseFilePath),
}))
fileObject.on('rerun', () => {
debug('file object rerun event')
return baseEmitter.emit('file:updated', filePath)
})
baseEmitter.once('close', () => {
debug('base emitter native close event')
return fileObject.emit('close')
})
}
if (config.isTextTerminal && (fileProcessor = fileProcessors[filePath])) {
debug('headless and already processed')
return fileProcessor
}
const preprocessor = (fileProcessors[filePath] = Promise.try(() => {
const span = telemetry.startSpan({ name: 'file:preprocessor' })
return plugins.execute('file:preprocessor', fileObject).then((arg) => {
span?.setAttribute('file', arg)
span?.end()
return arg
})
}))
return preprocessor
},
removeFile (filePath, config) {
let fileObject
filePath = path.resolve(config.projectRoot, filePath)
if (!fileProcessors[filePath]) {
return
}
debug(`removeFile ${filePath}`)
baseEmitter.emit('close', filePath)
fileObject = fileObjects[filePath]
if (fileObject) {
fileObject.emit('close')
}
delete fileObjects[filePath]
delete fileProcessors[filePath]
},
close () {
debug('close preprocessor')
fileObjects = {}
fileProcessors = {}
baseEmitter.emit('close')
baseEmitter.removeAllListeners()
},
}
module.exports = API