Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cloud recommendation message to CI output #24680

Merged
merged 24 commits into from Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3d90508
feat: add cloud recommendation message to CI output
astone123 Nov 14, 2022
dda9324
feat: remove --record flag for UI tests to demonstrate cloud message
astone123 Nov 14, 2022
5b3af89
feat: fix types
astone123 Nov 14, 2022
6649647
feat: don't record UI test runs
astone123 Nov 14, 2022
9781e3f
feat: remove more params to stop recording UI runs
astone123 Nov 14, 2022
3faaa2a
feat: fix types
astone123 Nov 14, 2022
166a994
feat: fix tests
astone123 Nov 14, 2022
12fa855
feat: use env.get
astone123 Nov 14, 2022
606fcec
feat: override environment variables for tests
astone123 Nov 14, 2022
396c650
feat: fix remaining tests
astone123 Nov 14, 2022
b067b6a
Merge branch 'develop' of github.com:cypress-io/cypress into astone12…
astone123 Nov 15, 2022
4bc9b36
chore: feedback
astone123 Nov 15, 2022
2a065a1
chore: follow new brand standards
astone123 Nov 15, 2022
347f73e
feat: feedback
astone123 Nov 16, 2022
a5a44e3
feat: simplify tests and snapshots
astone123 Nov 17, 2022
c71faec
feat: fix cypress_spec.js
astone123 Nov 17, 2022
55ec51b
feat: update log appearance; intentionally fail test and don't record
astone123 Nov 17, 2022
b56b026
feat: add log to figure out why message is not displaying
astone123 Nov 17, 2022
f4df2c5
feat: is-ci is returning false when running in Circle for some reason
astone123 Nov 17, 2022
3934092
feat: try updating server package file
astone123 Nov 17, 2022
0500820
feat: try adding modules to force-no-rewrite
astone123 Nov 17, 2022
1768a3b
feat: fix test, remove logs, fix CI config
astone123 Nov 17, 2022
4fd58ef
feat: remove unnecessary entry from force-no-rewrite
astone123 Nov 18, 2022
5fbabd4
feat: empty commit because Percy
astone123 Nov 18, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli/types/cypress-npm-api.d.ts
Expand Up @@ -229,6 +229,7 @@ declare namespace CypressCommandLine {
startedAt: dateTimeISO
endedAt: dateTimeISO
duration: ms
wallClockDuration?: number
}
/**
* Reporter name like "spec"
Expand Down Expand Up @@ -259,8 +260,10 @@ declare namespace CypressCommandLine {
* resolved filename of the spec
*/
absolute: string
relativeToCommonRoot: string
}
shouldUploadVideo: boolean
skippedSpec: boolean
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/config/test/project/utils.spec.ts
Expand Up @@ -27,6 +27,10 @@ import path from 'node:path'
const debug = Debug('test')

describe('config/src/project/utils', () => {
beforeEach(function () {
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS
})

before(function () {
this.env = process.env;

Expand Down
4 changes: 4 additions & 0 deletions packages/config/test/utils.spec.ts
Expand Up @@ -10,6 +10,10 @@ import {
} from '../src/project/utils'

describe('config/src/utils', () => {
beforeEach(function () {
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS
})

describe('hideKeys', () => {
it('removes middle part of the string', () => {
const hidden = hideKeys('12345-xxxx-abcde')
Expand Down
56 changes: 56 additions & 0 deletions packages/server/__snapshots__/cypress_spec.js
Expand Up @@ -351,3 +351,59 @@ exports['Long Dashboard URL'] = `
Recorded Run: http://dashboard.cypress.io/this-is-a-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-long-url

`

exports['CLOUD_RECOMMENDATION_MESSAGE'] = `

====================================================================================================

(Run Starting)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 1 found (test1.js) │
│ Searched: tests/test1.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

Running: test1.js (1 of 1)

(Results)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: undefined │
│ Passing: undefined │
│ Failing: 1 │
│ Pending: undefined │
│ Skipped: undefined │
│ Screenshots: 0 │
│ Video: false │
│ Duration: undefined seconds │
│ Spec Ran: test1.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘


====================================================================================================

(Run Finished)


Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✖ test1.js XX:XX - - 1 - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✖ 1 of 1 failed (100%) XX:XX - - 1 - -

----------------------------------------------------------------------------------------------------

Having trouble debugging your CI failures?

Record your runs to Cypress Cloud to watch video recordings for each test,
debug failing and flaky tests, and integrate with your favorite tools.

>> https://on.cypress.io/cloud-get-started

----------------------------------------------------------------------------------------------------
`
1 change: 1 addition & 0 deletions packages/server/lib/modes/run.ts
Expand Up @@ -1012,6 +1012,7 @@ async function ready (options: { projectRoot: string, record: boolean, key: stri

if (!options.quiet) {
printResults.renderSummaryTable(runUrl, results)
printResults.maybeLogCloudRecommendationMessage(results.runs || [], record)
}

return results
Expand Down
5 changes: 5 additions & 0 deletions packages/server/lib/util/ci_provider.js
@@ -1,6 +1,9 @@
const _ = require('lodash')
const isCi = require('is-ci')
const debug = require('debug')('cypress:server')

const getIsCi = () => isCi

const join = (char, ...pieces) => {
return _.chain(pieces).compact().join(char).value()
}
Expand Down Expand Up @@ -668,6 +671,8 @@ const detectableCiBuildIdProviders = () => {
}

module.exports = {
getIsCi,

list,

provider,
Expand Down
26 changes: 24 additions & 2 deletions packages/server/lib/util/print-run.ts
Expand Up @@ -9,6 +9,7 @@ import duration from './duration'
import newlines from './newlines'
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 { Cfg } from '../project-base'
Expand All @@ -22,11 +23,18 @@ type Screenshot = {
specName: string
}

export const cloudRecommendationMessage = `
Having trouble debugging your CI failures?

Record your runs to Cypress Cloud to watch video recordings for each test,
debug failing and flaky tests, and integrate with your favorite tools.
`

function color (val: any, c: string) {
return chalk[c](val)
}

function gray (val: any) {
export function gray (val: any) {
return color(val, 'gray')
}

Expand Down Expand Up @@ -274,7 +282,21 @@ export function displaySpecHeader (name: string, curr: number, total: number, es
}
}

export function renderSummaryTable (runUrl: string | undefined, results: any) {
export function maybeLogCloudRecommendationMessage (runs: CypressCommandLine.RunResult[], record: boolean) {
if (!getIsCi() || env.get('CYPRESS_COMMERCIAL_RECOMMENDATIONS') === '0' || record) {
return
}

if (runs.some((run) => run.stats.failures > 0)) {
terminal.divider('-')
console.log(cloudRecommendationMessage)
console.log(` >>`, color('https://on.cypress.io/cloud-get-started', 'cyan'))
console.log('')
terminal.divider('-')
}
}

export function renderSummaryTable (runUrl: string | undefined, results: CypressCommandLine.CypressRunResult) {
const { runs } = results

console.log('')
Expand Down
1 change: 1 addition & 0 deletions packages/server/package.json
Expand Up @@ -73,6 +73,7 @@
"http-proxy": "https://github.com/cypress-io/node-http-proxy.git#9322b4b69b34f13a6f3874e660a35df3305179c6",
"human-interval": "1.0.0",
"image-size": "0.8.3",
"is-ci": "^3.0.0",
"is-fork-pr": "2.5.0",
"is-html": "2.0.0",
"jimp": "0.14.0",
Expand Down
77 changes: 77 additions & 0 deletions packages/server/test/integration/cypress_spec.js
Expand Up @@ -44,6 +44,7 @@ const electronApp = require('../../lib/util/electron-app')
const savedState = require(`../../lib/saved_state`)
const { getCtx, clearCtx, setCtx, makeDataContext } = require(`../../lib/makeDataContext`)
const { BrowserCriClient } = require(`../../lib/browsers/browser-cri-client`)
const { cloudRecommendationMessage } = require('../../lib/util/print-run')

const TYPICAL_BROWSERS = [
{
Expand Down Expand Up @@ -349,6 +350,58 @@ describe('lib/cypress', () => {
sinon.stub(commitInfo, 'getRemoteOrigin').resolves('remoteOrigin')
})

describe('cloud recommendation message', () => {
it('gets logged when in CI and there is a failure', function () {
const relativePath = path.relative(cwd(), this.todosPath)

sinon.stub(ciProvider, 'getIsCi').returns(true)
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS
globalThis.CY_TEST_MOCK.listenForProjectEnd = { stats: { failures: 1 } }

return cypress.start([`--run-project=${this.todosPath}`, `--spec=${relativePath}/tests/test1.js`]).then(() => {
expect(console.log).to.be.calledWith(cloudRecommendationMessage)

snapshotConsoleLogs('CLOUD_RECOMMENDATION_MESSAGE')
})
})

it('does not get logged if CYPRESS_COMMERCIAL_RECOMMENDATIONS is set to 0', function () {
const relativePath = path.relative(cwd(), this.todosPath)

sinon.stub(ciProvider, 'getIsCi').returns(true)
process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS = '0'
globalThis.CY_TEST_MOCK.listenForProjectEnd = { stats: { failures: 1 } }

return cypress.start([`--run-project=${this.todosPath}`, `--spec=${relativePath}/tests/test1.js`]).then(() => {
expect(console.log).not.to.be.calledWith(cloudRecommendationMessage)
})
})

it('does not get logged if all tests pass', function () {
const relativePath = path.relative(cwd(), this.todosPath)

sinon.stub(ciProvider, 'getIsCi').returns(true)
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS
globalThis.CY_TEST_MOCK.listenForProjectEnd = { stats: { failures: 0 } }

return cypress.start([`--run-project=${this.todosPath}`, `--spec=${relativePath}/tests/test1.js`]).then(() => {
expect(console.log).not.to.be.calledWith(cloudRecommendationMessage)
})
})

it('does not get logged if not running in CI', function () {
const relativePath = path.relative(cwd(), this.todosPath)

sinon.stub(ciProvider, 'getIsCi').returns(undefined)
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS
globalThis.CY_TEST_MOCK.listenForProjectEnd = { stats: { failures: 1 } }
astone123 marked this conversation as resolved.
Show resolved Hide resolved

return cypress.start([`--run-project=${this.todosPath}`, `--spec=${relativePath}/tests/test1.js`]).then(() => {
expect(console.log).not.to.be.calledWith(cloudRecommendationMessage)
})
})
})

it('runs project headlessly and exits with exit code 0', function () {
return cypress.start([`--run-project=${this.todosPath}`])
.then(() => {
Expand Down Expand Up @@ -881,6 +934,10 @@ describe('lib/cypress', () => {
})

describe('config overrides', () => {
beforeEach(function () {
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS
})

it('can override default values', function () {
return cypress.start([`--run-project=${this.todosPath}`, '--config=requestTimeout=1234,videoCompression=false'])
.then(() => {
Expand Down Expand Up @@ -1069,6 +1126,7 @@ describe('lib/cypress', () => {
describe('--env', () => {
beforeEach(() => {
process.env = _.omit(process.env, 'CYPRESS_DEBUG')
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS

globalThis.CY_TEST_MOCK.listenForProjectEnd = { stats: { failures: 0 } }
})
Expand Down Expand Up @@ -1566,6 +1624,25 @@ describe('lib/cypress', () => {
return snapshotConsoleLogs('DASHBOARD_STALE_RUN 1')
})
})

describe('cloud recommendation message', () => {
it('does not display if --record is passed', function () {
sinon.stub(ciProvider, 'getIsCi').returns(true)
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS
globalThis.CY_TEST_MOCK.listenForProjectEnd = { stats: { failures: 1 } }

return cypress.start([
`--run-project=${this.recordPath}`,
'--record',
'--key=token-123',
'--group=electron-smoke-tests',
'--ciBuildId=ciBuildId123',
])
.then(() => {
expect(console.log).not.to.be.calledWith(cloudRecommendationMessage)
})
})
})
})

context('--return-pkg', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/server/test/unit/config_spec.js
Expand Up @@ -25,6 +25,8 @@ describe('lib/config', () => {

context('.get', () => {
beforeEach(async function () {
delete process.env.CYPRESS_COMMERCIAL_RECOMMENDATIONS

this.ctx = getCtx()

this.projectRoot = '/_test-output/path/to/project'
Expand Down
2 changes: 2 additions & 0 deletions tooling/v8-snapshot/src/setup/force-no-rewrite.ts
Expand Up @@ -69,4 +69,6 @@ export default [
'node_modules/prettier/parser-meriyah.js',
'node_modules/prettier/parser-typescript.js',
'node_modules/prettier/third-party.js',
'packages/server/node_modules/is-ci/index.js',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do these need to be in the force-no-rewrite category?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm trying to figure out exactly why, but it wouldn't work without adding those (they probably don't all need to be in there).

I think what might be happening is that the exported value from is-ci is getting evaluated and snapshotted before process.env.CI=true is set, and then it never gets re-evaluated?

If I don't have these in this file then is-ci returns false even though we are running in CI and it should be true. @ZachJW34 originally suggested trying this when we were working through it

Copy link
Contributor

Choose a reason for hiding this comment

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

@astone123 and I paired on this a bit. Not sure we need every one of these but it wasn't working without this addition. I believe it's due to how is-ci and ci-info are written, where the export is evaluated upon require e.g. module.exports = !!process.env.CI and not a function.

Could be totally wrong though!

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think from playing around locally all we need to add here is the first two and I'm good with that for now. Though I still want to figure a little more on why.

'packages/server/node_modules/ci-info/index.js',
]