Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions packages/data-context/src/DataContext.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { LaunchArgs, OpenProjectLaunchOptions, PlatformName } from '@packages/types'
import type { LaunchArgs, OpenProjectLaunchOptions } from '@packages/types'
import fsExtra from 'fs-extra'
import path from 'path'

Expand Down Expand Up @@ -28,7 +28,7 @@ import type { GraphQLSchema } from 'graphql'
import type { Server } from 'http'
import type { AddressInfo } from 'net'
import EventEmitter from 'events'
import type { App as ElectronApp } from 'electron'
import type { App, App as ElectronApp } from 'electron'
import { VersionsDataSource } from './sources/VersionsDataSource'

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

export interface DataContextConfig {
schema: GraphQLSchema
os: PlatformName
launchArgs: LaunchArgs
launchOptions: OpenProjectLaunchOptions
electronApp?: ElectronApp
Expand All @@ -55,13 +54,10 @@ export interface DataContextConfig {
authApi: AuthApiShape
projectApi: ProjectApiShape
electronApi: ElectronApiShape
/**
* Internal options used for testing purposes
*/
_internalOptions: InternalDataContextOptions
}

export class DataContext {
private _app?: App
private _rootBus: EventEmitter
private _coreData: CoreDataShape
private _gqlServer?: Server
Expand All @@ -73,6 +69,10 @@ export class DataContext {
this._coreData = _config.coreData ?? makeCoreData()
}

setElectronApp (app: App) {
this._app = app
}

get electronApp () {
return this._config.electronApp
}
Expand Down Expand Up @@ -101,10 +101,8 @@ export class DataContext {
this.actions.app.refreshNodePath(),
]

if (this._config._internalOptions.loadCachedProjects) {
// load projects from cache on start
toAwait.push(this.actions.project.loadProjects())
}
// load projects from cache on start
toAwait.push(this.actions.project.loadProjects())

if (this._config.launchArgs.projectRoot) {
await this.actions.project.setActiveProject(this._config.launchArgs.projectRoot)
Expand Down Expand Up @@ -137,10 +135,6 @@ export class DataContext {
return this._rootBus
}

get os () {
return this._config.os
}

get launchArgs () {
return this._config.launchArgs
}
Expand Down
48 changes: 48 additions & 0 deletions packages/data-context/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
import type { DataContext } from './DataContext'

export {
DataContext,
} from './DataContext'

export type {
DataContextConfig,
} from './DataContext'

let ctx: DataContext | null = null

// globalPubSub.on('cleanup', clearCtx)

/**
* Shouldn't ever be called from runtime code, primarily for test situations where we need to
*/
export function clearCtx () {
ctx = null
}

/**
* Gets the current DataContext, used in situations where it's too much work
* to inject it deeply through the class hierearchy in legacy server code, but we
* need to reference it anyway, and for the time being we can assume
* there's only one for the lifecycle of the Electron app.
*/
export function getCtx () {
if (!ctx) {
throw new Error(`
Expected DataContext to already have been set via setCtx. If this is a
testing context, make sure you are calling "setCtx" in a before hook,
otherwise check the application flow.
`)
}

return ctx
}

/**
* Sets the current DataContext - happens at runtime when we startup Cypress
* in "open" / "run" mode, or during testing in a beforeEach, when we clear the context
*/
export function setCtx (_ctx: DataContext) {
if (ctx) {
throw new Error(`
The context has already been set. If this is occurring in a testing context,
make sure you are clearing the context. Otherwise
`)
}

ctx = _ctx

return _ctx
}
1 change: 1 addition & 0 deletions packages/data-context/src/util/urqlCacheKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const urqlCacheKeys: Partial<CacheExchangeOpts> = {
App: (data) => data.__typename,
DevState: (data) => data.__typename,
Wizard: (data) => data.__typename,
CloudRunCommitInfo: () => null,
GitInfo: () => null,
BaseError: () => null,
ProjectPreferences: (data) => data.__typename,
Expand Down
5 changes: 4 additions & 1 deletion packages/driver/src/cypress/proxy-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,10 @@ export default class ProxyLogging {
const proxyRequest = new ProxyRequest(preRequest)
const logConfig = getRequestLogConfig(proxyRequest as Omit<ProxyRequest, 'log'>)

proxyRequest.log = this.Cypress.log(logConfig).snapshot('request')
// TODO: Figure out what is causing the race condition here
if (this.Cypress.log) {
proxyRequest.log = this.Cypress.log(logConfig).snapshot('request')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

likely some check missing in event-manager


this.proxyRequests.push(proxyRequest as ProxyRequest)

Expand Down
12 changes: 10 additions & 2 deletions packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import path from 'path'
import type { RemoteGraphQLInterceptor, WithCtxInjected, WithCtxOptions } from './support/e2eSupport'
import { e2eProjectDirs } from './support/e2eProjectDirs'
import type { CloudExecuteRemote } from '@packages/data-context/src/sources'
import type { DataContext } from '@packages/data-context'
import * as inspector from 'inspector'
import sinonChai from '@cypress/sinon-chai'
import sinon from 'sinon'
Expand All @@ -14,6 +13,7 @@ import { Response } from 'cross-fetch'

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

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

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

on('task', {
beforeEach () {
clearCtx()
setCtx(makeDataContext({}))
remoteGraphQLIntercept = undefined

return null
},
remoteGraphQLIntercept (fn: string) {
remoteGraphQLIntercept = new Function('console', 'obj', `return (${fn})(obj)`).bind(null, console) as RemoteGraphQLInterceptor

Expand All @@ -74,7 +82,7 @@ export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEve
if (obj.activeTestId !== currentTestId) {
await ctx?.destroy()
currentTestId = obj.activeTestId
remoteGraphQLIntercept = undefined

testState = {};
({ serverPortPromise, ctx } = runInternalServer({
projectRoot: null,
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend-shared/cypress/e2e/support/e2eSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ beforeEach(() => {
// Reset the ports so we know we need to call "setupE2E" before each test
Cypress.env('e2e_serverPort', undefined)
Cypress.env('e2e_gqlPort', undefined)
cy.task('beforeEach', { log: false })
})

// function setup
Expand Down Expand Up @@ -131,7 +132,7 @@ function visitApp (href?: string) {
}

return cy.withCtx(async (ctx) => {
return JSON.stringify(ctx.html.fetchAppInitialData())
return JSON.stringify(await ctx.html.fetchAppInitialData())
}, { log: false }).then((ssrData) => {
return cy.visit(`dist-app/index.html?serverPort=${e2e_serverPort}${href || ''}`, {
onBeforeLoad (win) {
Expand Down
9 changes: 5 additions & 4 deletions packages/server/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import pathHelpers from './util/path_helpers'
const debug = Debug('cypress:server:config')

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

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

Expand Down Expand Up @@ -130,10 +129,9 @@ export type FullConfig =
export function get (
projectRoot,
options: { configFile?: string | false } = { configFile: undefined },
ctx: DataContext,
): Promise<FullConfig> {
return Promise.all([
settings.read(projectRoot, options, ctx).then(validateFile(options.configFile ?? 'cypress.config.{ts|js}')),
settings.read(projectRoot, options).then(validateFile(options.configFile ?? 'cypress.config.{ts|js}')),
settings.readEnv(projectRoot).then(validateFile('cypress.env.json')),
])
.spread((settings, envFile) => {
Expand Down Expand Up @@ -444,7 +442,10 @@ export function setSupportFileAndFolder (obj, defaults) {
// /tmp/foo -> /private/tmp/foo
// which can confuse the rest of the code
// switch it back to "normal" file
obj.supportFile = path.join(sf, path.basename(obj.supportFile))
const supportFileName = path.basename(obj.supportFile)
const base = sf.endsWith(supportFileName) ? path.dirname(sf) : sf

obj.supportFile = path.join(base, supportFileName)

return fs.pathExists(obj.supportFile)
.then((found) => {
Expand Down
48 changes: 7 additions & 41 deletions packages/server/lib/makeDataContext.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,29 @@
import { DataContext } from '@packages/data-context'
import os from 'os'
import electron, { App } from 'electron'
import { DataContext, getCtx, setCtx, clearCtx } from '@packages/data-context'
import electron from 'electron'

import specsUtil from './util/specs'
import type { AllowedState, FindSpecs, FoundBrowser, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions, PlatformName, Preferences, SettingsOptions } from '@packages/types'
import type { AllowedState, FindSpecs, FoundBrowser, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions, Preferences, SettingsOptions } from '@packages/types'
import browserUtils from './browsers/utils'
import auth from './gui/auth'
import user from './user'
import * as config from './config'
import { EventEmitter } from 'events'
import { openProject } from './open_project'
import cache from './cache'
import errors from './errors'
import findSystemNode from './util/find_system_node'
import { graphqlSchema } from '@packages/graphql/src/schema'
import type { InternalDataContextOptions } from '@packages/data-context/src/DataContext'
import { openExternal } from '@packages/server/lib/gui/links'
import { getUserEditor } from './util/editors'
import * as savedState from './saved_state'

const { getBrowsers, ensureAndGetByNameOrPath } = browserUtils

interface MakeDataContextOptions {
electronApp?: App
os: PlatformName
rootBus: EventEmitter
launchArgs: LaunchArgs
_internalOptions: InternalDataContextOptions
}

let legacyDataContext: DataContext | undefined

// For testing
export async function clearLegacyDataContext () {
await legacyDataContext?.destroy()
legacyDataContext = undefined
}

export function makeLegacyDataContext (launchArgs: LaunchArgs = {} as LaunchArgs): DataContext {
if (legacyDataContext && process.env.LAUNCHPAD) {
throw new Error(`Expected ctx to be passed as an arg, but used legacy data context`)
} else if (!legacyDataContext) {
legacyDataContext = makeDataContext({
rootBus: new EventEmitter,
launchArgs,
os: os.platform() as PlatformName,
_internalOptions: {
loadCachedProjects: true,
},
})
}

return legacyDataContext
}
export { getCtx, setCtx, clearCtx }

export function makeDataContext (options: MakeDataContextOptions): DataContext {
export function makeDataContext (launchArgs: LaunchArgs): DataContext {
const ctx = new DataContext({
schema: graphqlSchema,
...options,
launchArgs,
launchOptions: {},
appApi: {
getBrowsers,
Expand All @@ -79,7 +45,7 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext {
},
projectApi: {
getConfig (projectRoot: string, options?: SettingsOptions) {
return config.get(projectRoot, options, ctx)
return config.get(projectRoot, options)
},
launchProject (browser: FoundBrowser, spec: Cypress.Spec, options?: LaunchOpts) {
return openProject.launch({ ...browser }, spec, options)
Expand Down
12 changes: 8 additions & 4 deletions packages/server/lib/modes/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { makeLegacyDataContext } from '../makeDataContext'
import { clearCtx, setCtx } from '@packages/data-context'
import { makeDataContext } from '../makeDataContext'

export = (mode, options) => {
if (!process.env.LAUNCHPAD) {
makeLegacyDataContext(options)
}
// When we're in testing mode, this is setup automatically as a beforeEach
clearCtx()

const ctx = makeDataContext(options)

setCtx(ctx)

if (mode === 'record') {
return require('./record').run(options)
Expand Down
15 changes: 8 additions & 7 deletions packages/server/lib/modes/internal-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@ import type { App } from 'electron'
import { makeDataContext } from '../makeDataContext'
import { makeGraphQLServer } from '../gui/makeGraphQLServer'
import { assertValidPlatform } from '@packages/types/src/platform'
import { DataContext, getCtx, setCtx } from '@packages/data-context'

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

assertValidPlatform(platform)

const ctx = makeDataContext({
electronApp,
os: platform,
rootBus: bus,
launchArgs,
_internalOptions,
})
let ctx: DataContext

try {
ctx = getCtx()
} catch {
ctx = setCtx(makeDataContext(launchArgs))
}

// Initializing the data context, loading browsers, etc.
ctx.initializeData()
Expand Down
4 changes: 2 additions & 2 deletions packages/server/lib/open_project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getSpecUrl } from './project_utils'
import errors from './errors'
import type { LaunchOpts, LaunchArgs, OpenProjectLaunchOptions, FoundBrowser } from '@packages/types'
import type { DataContext } from '@packages/data-context'
import { makeLegacyDataContext } from './makeDataContext'
import { getCtx } from './makeDataContext'

const debug = Debug('cypress:server:open_project')

Expand Down Expand Up @@ -268,7 +268,7 @@ export class OpenProject {
_ctx?: DataContext

async create (path: string, args: LaunchArgs, options: OpenProjectLaunchOptions, browsers: FoundBrowser[] = []) {
this._ctx = options.ctx ?? makeLegacyDataContext()
this._ctx = getCtx()
debug('open_project create %s', path)

_.defaults(options, {
Expand Down
Loading