Skip to content

Commit 9d975ec

Browse files
authored
refactor: makeLegacyContext -> getCtx (#19308)
* refactor: makeLegacyContext -> getCtx * Fix tests & types * fix: failing tests * CI fixes
1 parent aa7b05d commit 9d975ec

35 files changed

+164
-126
lines changed

packages/data-context/src/DataContext.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { LaunchArgs, OpenProjectLaunchOptions, PlatformName } from '@packages/types'
1+
import type { LaunchArgs, OpenProjectLaunchOptions } from '@packages/types'
22
import fsExtra from 'fs-extra'
33
import path from 'path'
44

@@ -28,7 +28,7 @@ import type { GraphQLSchema } from 'graphql'
2828
import type { Server } from 'http'
2929
import type { AddressInfo } from 'net'
3030
import EventEmitter from 'events'
31-
import type { App as ElectronApp } from 'electron'
31+
import type { App, App as ElectronApp } from 'electron'
3232
import { VersionsDataSource } from './sources/VersionsDataSource'
3333

3434
const IS_DEV_ENV = process.env.CYPRESS_INTERNAL_ENV !== 'production'
@@ -39,7 +39,6 @@ export interface InternalDataContextOptions {
3939

4040
export interface DataContextConfig {
4141
schema: GraphQLSchema
42-
os: PlatformName
4342
launchArgs: LaunchArgs
4443
launchOptions: OpenProjectLaunchOptions
4544
electronApp?: ElectronApp
@@ -55,13 +54,10 @@ export interface DataContextConfig {
5554
authApi: AuthApiShape
5655
projectApi: ProjectApiShape
5756
electronApi: ElectronApiShape
58-
/**
59-
* Internal options used for testing purposes
60-
*/
61-
_internalOptions: InternalDataContextOptions
6257
}
6358

6459
export class DataContext {
60+
private _app?: App
6561
private _rootBus: EventEmitter
6662
private _coreData: CoreDataShape
6763
private _gqlServer?: Server
@@ -73,6 +69,10 @@ export class DataContext {
7369
this._coreData = _config.coreData ?? makeCoreData()
7470
}
7571

72+
setElectronApp (app: App) {
73+
this._app = app
74+
}
75+
7676
get electronApp () {
7777
return this._config.electronApp
7878
}
@@ -101,10 +101,8 @@ export class DataContext {
101101
this.actions.app.refreshNodePath(),
102102
]
103103

104-
if (this._config._internalOptions.loadCachedProjects) {
105-
// load projects from cache on start
106-
toAwait.push(this.actions.project.loadProjects())
107-
}
104+
// load projects from cache on start
105+
toAwait.push(this.actions.project.loadProjects())
108106

109107
if (this._config.launchArgs.projectRoot) {
110108
await this.actions.project.setActiveProject(this._config.launchArgs.projectRoot)
@@ -137,10 +135,6 @@ export class DataContext {
137135
return this._rootBus
138136
}
139137

140-
get os () {
141-
return this._config.os
142-
}
143-
144138
get launchArgs () {
145139
return this._config.launchArgs
146140
}

packages/data-context/src/index.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,55 @@
1+
import type { DataContext } from './DataContext'
2+
13
export {
24
DataContext,
35
} from './DataContext'
46

57
export type {
68
DataContextConfig,
79
} from './DataContext'
10+
11+
let ctx: DataContext | null = null
12+
13+
// globalPubSub.on('cleanup', clearCtx)
14+
15+
/**
16+
* Shouldn't ever be called from runtime code, primarily for test situations where we need to
17+
*/
18+
export function clearCtx () {
19+
ctx = null
20+
}
21+
22+
/**
23+
* Gets the current DataContext, used in situations where it's too much work
24+
* to inject it deeply through the class hierearchy in legacy server code, but we
25+
* need to reference it anyway, and for the time being we can assume
26+
* there's only one for the lifecycle of the Electron app.
27+
*/
28+
export function getCtx () {
29+
if (!ctx) {
30+
throw new Error(`
31+
Expected DataContext to already have been set via setCtx. If this is a
32+
testing context, make sure you are calling "setCtx" in a before hook,
33+
otherwise check the application flow.
34+
`)
35+
}
36+
37+
return ctx
38+
}
39+
40+
/**
41+
* Sets the current DataContext - happens at runtime when we startup Cypress
42+
* in "open" / "run" mode, or during testing in a beforeEach, when we clear the context
43+
*/
44+
export function setCtx (_ctx: DataContext) {
45+
if (ctx) {
46+
throw new Error(`
47+
The context has already been set. If this is occurring in a testing context,
48+
make sure you are clearing the context. Otherwise
49+
`)
50+
}
51+
52+
ctx = _ctx
53+
54+
return _ctx
55+
}

packages/data-context/src/util/urqlCacheKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const urqlCacheKeys: Partial<CacheExchangeOpts> = {
1313
App: (data) => data.__typename,
1414
DevState: (data) => data.__typename,
1515
Wizard: (data) => data.__typename,
16+
CloudRunCommitInfo: () => null,
1617
GitInfo: () => null,
1718
BaseError: () => null,
1819
ProjectPreferences: (data) => data.__typename,

packages/driver/src/cypress/proxy-logging.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,10 @@ export default class ProxyLogging {
392392
const proxyRequest = new ProxyRequest(preRequest)
393393
const logConfig = getRequestLogConfig(proxyRequest as Omit<ProxyRequest, 'log'>)
394394

395-
proxyRequest.log = this.Cypress.log(logConfig).snapshot('request')
395+
// TODO: Figure out what is causing the race condition here
396+
if (this.Cypress.log) {
397+
proxyRequest.log = this.Cypress.log(logConfig).snapshot('request')
398+
}
396399

397400
this.proxyRequests.push(proxyRequest as ProxyRequest)
398401

packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import path from 'path'
22
import type { RemoteGraphQLInterceptor, WithCtxInjected, WithCtxOptions } from './support/e2eSupport'
33
import { e2eProjectDirs } from './support/e2eProjectDirs'
44
import type { CloudExecuteRemote } from '@packages/data-context/src/sources'
5-
import type { DataContext } from '@packages/data-context'
65
import * as inspector from 'inspector'
76
import sinonChai from '@cypress/sinon-chai'
87
import sinon from 'sinon'
@@ -14,6 +13,7 @@ import { Response } from 'cross-fetch'
1413

1514
import { CloudRunQuery } from '../support/mock-graphql/stubgql-CloudTypes'
1615
import { getOperationName } from '@urql/core'
16+
import type { DataContext } from '@packages/data-context'
1717

1818
const cloudSchema = buildSchema(fs.readFileSync(path.join(__dirname, '../../../graphql/schemas/cloud.graphql'), 'utf8'))
1919

@@ -36,6 +36,7 @@ export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEve
3636
delete process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT
3737
// require'd so we don't import the types from @packages/server which would
3838
// pollute strict type checking
39+
const { clearCtx, makeDataContext, setCtx } = require('../../../server/lib/makeDataContext')
3940
const { runInternalServer } = require('@packages/server/lib/modes/internal-server')
4041
const Fixtures = require('@tooling/system-tests/lib/fixtures')
4142
const tmpDir = path.join(__dirname, '.projects')
@@ -59,6 +60,13 @@ export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEve
5960
let remoteGraphQLIntercept: RemoteGraphQLInterceptor | undefined
6061

6162
on('task', {
63+
beforeEach () {
64+
clearCtx()
65+
setCtx(makeDataContext({}))
66+
remoteGraphQLIntercept = undefined
67+
68+
return null
69+
},
6270
remoteGraphQLIntercept (fn: string) {
6371
remoteGraphQLIntercept = new Function('console', 'obj', `return (${fn})(obj)`).bind(null, console) as RemoteGraphQLInterceptor
6472

@@ -74,7 +82,7 @@ export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEve
7482
if (obj.activeTestId !== currentTestId) {
7583
await ctx?.destroy()
7684
currentTestId = obj.activeTestId
77-
remoteGraphQLIntercept = undefined
85+
7886
testState = {};
7987
({ serverPortPromise, ctx } = runInternalServer({
8088
projectRoot: null,

packages/frontend-shared/cypress/e2e/support/e2eSupport.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ beforeEach(() => {
7171
// Reset the ports so we know we need to call "setupE2E" before each test
7272
Cypress.env('e2e_serverPort', undefined)
7373
Cypress.env('e2e_gqlPort', undefined)
74+
cy.task('beforeEach', { log: false })
7475
})
7576

7677
// function setup
@@ -131,7 +132,7 @@ function visitApp (href?: string) {
131132
}
132133

133134
return cy.withCtx(async (ctx) => {
134-
return JSON.stringify(ctx.html.fetchAppInitialData())
135+
return JSON.stringify(await ctx.html.fetchAppInitialData())
135136
}, { log: false }).then((ssrData) => {
136137
return cy.visit(`dist-app/index.html?serverPort=${e2e_serverPort}${href || ''}`, {
137138
onBeforeLoad (win) {

packages/server/lib/config.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import pathHelpers from './util/path_helpers'
1717
const debug = Debug('cypress:server:config')
1818

1919
import { getProcessEnvVars, CYPRESS_SPECIAL_ENV_VARS } from './util/config'
20-
import type { DataContext } from '@packages/data-context'
2120

2221
const folders = _(configUtils.options).filter({ isFolder: true }).map('name').value()
2322

@@ -130,10 +129,9 @@ export type FullConfig =
130129
export function get (
131130
projectRoot,
132131
options: { configFile?: string | false } = { configFile: undefined },
133-
ctx: DataContext,
134132
): Promise<FullConfig> {
135133
return Promise.all([
136-
settings.read(projectRoot, options, ctx).then(validateFile(options.configFile ?? 'cypress.config.{ts|js}')),
134+
settings.read(projectRoot, options).then(validateFile(options.configFile ?? 'cypress.config.{ts|js}')),
137135
settings.readEnv(projectRoot).then(validateFile('cypress.env.json')),
138136
])
139137
.spread((settings, envFile) => {
@@ -444,7 +442,10 @@ export function setSupportFileAndFolder (obj, defaults) {
444442
// /tmp/foo -> /private/tmp/foo
445443
// which can confuse the rest of the code
446444
// switch it back to "normal" file
447-
obj.supportFile = path.join(sf, path.basename(obj.supportFile))
445+
const supportFileName = path.basename(obj.supportFile)
446+
const base = sf.endsWith(supportFileName) ? path.dirname(sf) : sf
447+
448+
obj.supportFile = path.join(base, supportFileName)
448449

449450
return fs.pathExists(obj.supportFile)
450451
.then((found) => {

packages/server/lib/makeDataContext.ts

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,29 @@
1-
import { DataContext } from '@packages/data-context'
2-
import os from 'os'
3-
import electron, { App } from 'electron'
1+
import { DataContext, getCtx, setCtx, clearCtx } from '@packages/data-context'
2+
import electron from 'electron'
43

54
import specsUtil from './util/specs'
6-
import type { AllowedState, FindSpecs, FoundBrowser, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions, PlatformName, Preferences, SettingsOptions } from '@packages/types'
5+
import type { AllowedState, FindSpecs, FoundBrowser, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions, Preferences, SettingsOptions } from '@packages/types'
76
import browserUtils from './browsers/utils'
87
import auth from './gui/auth'
98
import user from './user'
109
import * as config from './config'
11-
import { EventEmitter } from 'events'
1210
import { openProject } from './open_project'
1311
import cache from './cache'
1412
import errors from './errors'
1513
import findSystemNode from './util/find_system_node'
1614
import { graphqlSchema } from '@packages/graphql/src/schema'
17-
import type { InternalDataContextOptions } from '@packages/data-context/src/DataContext'
1815
import { openExternal } from '@packages/server/lib/gui/links'
1916
import { getUserEditor } from './util/editors'
2017
import * as savedState from './saved_state'
2118

2219
const { getBrowsers, ensureAndGetByNameOrPath } = browserUtils
2320

24-
interface MakeDataContextOptions {
25-
electronApp?: App
26-
os: PlatformName
27-
rootBus: EventEmitter
28-
launchArgs: LaunchArgs
29-
_internalOptions: InternalDataContextOptions
30-
}
31-
32-
let legacyDataContext: DataContext | undefined
33-
34-
// For testing
35-
export async function clearLegacyDataContext () {
36-
await legacyDataContext?.destroy()
37-
legacyDataContext = undefined
38-
}
39-
40-
export function makeLegacyDataContext (launchArgs: LaunchArgs = {} as LaunchArgs): DataContext {
41-
if (legacyDataContext && process.env.LAUNCHPAD) {
42-
throw new Error(`Expected ctx to be passed as an arg, but used legacy data context`)
43-
} else if (!legacyDataContext) {
44-
legacyDataContext = makeDataContext({
45-
rootBus: new EventEmitter,
46-
launchArgs,
47-
os: os.platform() as PlatformName,
48-
_internalOptions: {
49-
loadCachedProjects: true,
50-
},
51-
})
52-
}
53-
54-
return legacyDataContext
55-
}
21+
export { getCtx, setCtx, clearCtx }
5622

57-
export function makeDataContext (options: MakeDataContextOptions): DataContext {
23+
export function makeDataContext (launchArgs: LaunchArgs): DataContext {
5824
const ctx = new DataContext({
5925
schema: graphqlSchema,
60-
...options,
26+
launchArgs,
6127
launchOptions: {},
6228
appApi: {
6329
getBrowsers,
@@ -79,7 +45,7 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext {
7945
},
8046
projectApi: {
8147
getConfig (projectRoot: string, options?: SettingsOptions) {
82-
return config.get(projectRoot, options, ctx)
48+
return config.get(projectRoot, options)
8349
},
8450
launchProject (browser: FoundBrowser, spec: Cypress.Spec, options?: LaunchOpts) {
8551
return openProject.launch({ ...browser }, spec, options)

packages/server/lib/modes/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { makeLegacyDataContext } from '../makeDataContext'
1+
import { clearCtx, setCtx } from '@packages/data-context'
2+
import { makeDataContext } from '../makeDataContext'
23

34
export = (mode, options) => {
4-
if (!process.env.LAUNCHPAD) {
5-
makeLegacyDataContext(options)
6-
}
5+
// When we're in testing mode, this is setup automatically as a beforeEach
6+
clearCtx()
7+
8+
const ctx = makeDataContext(options)
9+
10+
setCtx(ctx)
711

812
if (mode === 'record') {
913
return require('./record').run(options)

packages/server/lib/modes/internal-server.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@ import type { App } from 'electron'
55
import { makeDataContext } from '../makeDataContext'
66
import { makeGraphQLServer } from '../gui/makeGraphQLServer'
77
import { assertValidPlatform } from '@packages/types/src/platform'
8+
import { DataContext, getCtx, setCtx } from '@packages/data-context'
89

910
export function runInternalServer (launchArgs, _internalOptions = { loadCachedProjects: true }, electronApp?: App) {
1011
const bus = new EventEmitter()
1112
const platform = os.platform()
1213

1314
assertValidPlatform(platform)
1415

15-
const ctx = makeDataContext({
16-
electronApp,
17-
os: platform,
18-
rootBus: bus,
19-
launchArgs,
20-
_internalOptions,
21-
})
16+
let ctx: DataContext
17+
18+
try {
19+
ctx = getCtx()
20+
} catch {
21+
ctx = setCtx(makeDataContext(launchArgs))
22+
}
2223

2324
// Initializing the data context, loading browsers, etc.
2425
ctx.initializeData()

0 commit comments

Comments
 (0)