Skip to content

Commit

Permalink
Merge branch 'develop' into update-v8-snapshot-cache-on-develop
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanthemanuel authored May 24, 2023
2 parents dc047ef + d91177a commit af2f83c
Show file tree
Hide file tree
Showing 27 changed files with 662 additions and 29 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
<a href="https://www.npmjs.com/package/cypress">
<img src="https://img.shields.io/npm/dm/cypress.svg" alt="npm"/>
</a>
<a href="https://gitter.im/cypress-io/cypress">
<img src="https://img.shields.io/gitter/room/cypress-io/cypress.svg" alt="Gitter chat"/>
<a href="https://on.cypress.io/discord">
<img src="https://img.shields.io/badge/chat-on%20Discord-brightgreen" alt="Discord chat"/>
</a>
<a href="https://stackshare.io/cypress">
<img src="https://img.stackshare.io/misc/follow-on-stackshare-badge.svg" alt="StackShare"/>
Expand Down
33 changes: 33 additions & 0 deletions packages/app/cypress/e2e/cypress-in-cypress.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,37 @@ describe('Cypress in Cypress', { viewportWidth: 1500, defaultCommandTimeout: 100
expect(ctx.actions.project.initializeActiveProject).to.be.called
})
})

describe('runSpec mutation', () => {
it('should trigger expected spec from POST', () => {
startAtSpecsPage('e2e')

cy.contains('E2E specs').should('be.visible')

cy.withCtx(async (ctx) => {
const url = `http://127.0.0.1:${ctx.gqlServerPort}/__launchpad/graphql?`
const payload = `{"query":"mutation{\\nrunSpec(specPath:\\"cypress/e2e/dom-content.spec.js\\"){\\n__typename\\n... on RunSpecResponse{\\ntestingType\\nbrowser{\\nid\\nname\\n}\\nspec{\\nid\\nname\\n}\\n}\\n}\\n}","variables":null}`

ctx.coreData.app.browserStatus = 'open'

/*
Note: If this test starts failing, this fetch is the likely culprit.
Validate the GQL payload above is still valid by logging the fetch response JSON
*/

await ctx.util.fetch(
url,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: payload,
},
)
})

cy.contains('Dom Content').should('be.visible')
})
})
})
9 changes: 5 additions & 4 deletions packages/app/src/runner/event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@ export class EventManager {
runnerUiStore.setAutomationStatus(connected)
})

this.ws.on('change:to:url', (url) => {
window.location.href = url
})

this.ws.on('update:telemetry:context', (contextString) => {
const context = JSON.parse(contextString)

Expand Down Expand Up @@ -815,7 +811,12 @@ export class EventManager {

stop () {
this.localBus.removeAllListeners()

// Grab existing listeners for url change event, we want to preserve them
const urlChangeListeners = this.ws.listeners('change:to:url')

this.ws.off()
urlChangeListeners.forEach((listener) => this.ws.on('change:to:url', listener))
}

async teardown (state: MobxRunnerStore, isRerun = false) {
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export function createWebsocket (config: Cypress.Config) {
ws.emit('runner:connected')
})

ws.on('change:to:url', (url) => {
window.location.href = url
})

return ws
}

Expand Down
2 changes: 2 additions & 0 deletions packages/config/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'lodash'
import Debug from 'debug'
import {
defaultSpecPattern,
defaultExcludeSpecPattern,
options,
breakingOptions,
breakingRootOptions,
Expand All @@ -17,6 +18,7 @@ import * as validation from './validation'

export {
defaultSpecPattern,
defaultExcludeSpecPattern,
options,
breakingOptions,
BreakingOption,
Expand Down
7 changes: 6 additions & 1 deletion packages/config/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export const defaultSpecPattern = {
component: '**/*.cy.{js,jsx,ts,tsx}',
}

export const defaultExcludeSpecPattern = {
e2e: '*.hot-update.js',
component: ['**/__snapshots__/*', '**/__image_snapshots__/*'],
}

// NOTE:
// If you add/remove/change a config value, make sure to update the following
// - cli/types/index.d.ts (including allowed config options on TestOptions)
Expand Down Expand Up @@ -271,7 +276,7 @@ const driverConfigOptions: Array<DriverConfigOption> = [
requireRestartOnChange: 'server',
}, {
name: 'excludeSpecPattern',
defaultValue: (options: Record<string, any> = {}) => options.testingType === 'component' ? ['**/__snapshots__/*', '**/__image_snapshots__/*'] : '*.hot-update.js',
defaultValue: (options: Record<string, any> = {}) => options.testingType === 'component' ? defaultExcludeSpecPattern.component : defaultExcludeSpecPattern.e2e,
validation: validate.isStringOrArrayOfStrings,
overrideLevel: 'any',
}, {
Expand Down
17 changes: 16 additions & 1 deletion packages/data-context/src/actions/ElectronActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { App, BrowserWindow, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron'
import type { App, BrowserWindow, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue, Notification } from 'electron'
import os from 'os'
import type { DataContext } from '..'
import _ from 'lodash'
Expand All @@ -13,6 +13,7 @@ export interface ElectronApiShape {
copyTextToClipboard(text: string): void
isMainWindowFocused(): boolean
focusMainWindow(): void
createNotification(title: string, body: string): Notification
}

export class ElectronActions {
Expand Down Expand Up @@ -104,4 +105,18 @@ export class ElectronActions {
return obj.filePath || null
})
}

showSystemNotification (title: string, body: string, onClick?: () => void) {
const notification = this.ctx.electronApi.createNotification(title, body)

const defaultOnClick = async () => {
await this.ctx.actions.browser.focusActiveBrowserWindow()
}

const clickHandler = onClick || defaultOnClick

notification.on('click', clickHandler)

notification.show()
}
}
172 changes: 172 additions & 0 deletions packages/data-context/src/actions/ProjectActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ import templates from '../codegen/templates'
import { insertValuesInConfigFile } from '../util'
import { getError } from '@packages/errors'
import { resetIssuedWarnings } from '@packages/config'
import type { RunSpecErrorCode } from '@packages/graphql/src/schemaTypes'
import debugLib from 'debug'

export class RunSpecError extends Error {
constructor (public code: typeof RunSpecErrorCode[number], msg: string) {
super(msg)
}
}

export interface ProjectApiShape {
/**
Expand Down Expand Up @@ -46,6 +54,7 @@ export interface ProjectApiShape {
isListening: (url: string) => Promise<void>
resetBrowserTabsForNextTest(shouldKeepTabOpen: boolean): Promise<void>
resetServer(): void
runSpec(spec: Cypress.Spec): Promise<void>
}

export interface FindSpecs<T> {
Expand Down Expand Up @@ -76,6 +85,8 @@ type SetForceReconfigureProjectByTestingType = {
testingType?: TestingType
}

const debug = debugLib('cypress:data-context:ProjectActions')

export class ProjectActions {
constructor (private ctx: DataContext) {}

Expand Down Expand Up @@ -462,4 +473,165 @@ export class ProjectActions {
await this.ctx.actions.wizard.scaffoldTestingType()
}
}

async runSpec ({ specPath }: { specPath: string}) {
const waitForBrowserToOpen = async () => {
const browserStatusSubscription = this.ctx.emitter.subscribeTo('browserStatusChange', { sendInitial: false })

// Wait for browser to finish launching. Browser is either launched from scratch
// or relaunched when switching testing types - we need to wait in either case
// We wait a maximum of 3 seconds so we don't block indefinitely in case something
// goes sideways with the browser launch process. This is broken up into three
// separate 'waits' in case we have to watch a browser relaunch (close > opening > open)
debug('Waiting for browser to report `open`')
let maxIterations = 3

while (this.ctx.coreData.app.browserStatus !== 'open') {
await Promise.race([
new Promise((resolve) => setTimeout(resolve, 1000)),
browserStatusSubscription.next(),
])

if (--maxIterations === 0) {
break
}
}

await browserStatusSubscription.return(undefined as any)
}

try {
if (!this.ctx.currentProject) {
throw new RunSpecError('NO_PROJECT', 'A project must be open prior to attempting to run a spec')
}

if (!specPath) {
throw new RunSpecError('NO_SPEC_PATH', '`specPath` must be a non-empty string')
}

let targetTestingType: TestingType

// Check to see whether input specPath matches the specPattern for one or the other testing type
// If it matches neither then we can't run the spec and we should error
if (await this.ctx.project.matchesSpecPattern(specPath, 'e2e')) {
targetTestingType = 'e2e'
} else if (await this.ctx.project.matchesSpecPattern(specPath, 'component')) {
targetTestingType = 'component'
} else {
throw new RunSpecError('NO_SPEC_PATTERN_MATCH', 'Unable to determine testing type, spec does not match any configured specPattern')
}

debug(`Spec %s matches '${targetTestingType}' pattern`, specPath)

const absoluteSpecPath = this.ctx.path.resolve(this.ctx.currentProject, specPath)

debug('Attempting to launch spec %s', absoluteSpecPath)

// Look to see if there's actually a file at the target location
// This helps us avoid switching testingType *then* finding out the spec doesn't exist
if (!this.ctx.fs.existsSync(absoluteSpecPath)) {
throw new RunSpecError('SPEC_NOT_FOUND', `No file exists at path ${absoluteSpecPath}`)
}

// We now know what testingType we need to be in - if we're already there, great
// If not, verify that type is configured then switch (or throw an error if not configured)
if (this.ctx.coreData.currentTestingType !== targetTestingType) {
if (!this.ctx.lifecycleManager.isTestingTypeConfigured(targetTestingType)) {
throw new RunSpecError('TESTING_TYPE_NOT_CONFIGURED', `Input path matched specPattern for '${targetTestingType}' testing type, but it is not configured.`)
}

debug('Setting testing type to %s', targetTestingType)

const specChangeSubscription = this.ctx.emitter.subscribeTo('specsChange', { sendInitial: false })

const originalTestingType = this.ctx.coreData.currentTestingType

// Temporarily toggle testing type so the `activeBrowser` can be initialized
// for the targeted testing type. Browser has to be initialized prior to our "relaunch"
// call below - this can be an issue when Cypress is still on the launchpad and no
// browser has been launched yet
this.ctx.lifecycleManager.setCurrentTestingType(targetTestingType)
await this.ctx.lifecycleManager.setInitialActiveBrowser()
this.ctx.lifecycleManager.setCurrentTestingType(originalTestingType)

// This is the magic sauce - we now have a browser selected, so this will toggle
// the testing type, trigger specs to update, and launch the browser
await this.switchTestingTypesAndRelaunch(targetTestingType)

await waitForBrowserToOpen()

// When testing type changes we need to wait for the specWatcher to trigger and load new specs
// otherwise our call to `getCurrentSpecByAbsolute` below will fail
// Wait a maximum of 2 seconds just in case something breaks with the event subscription
// so we don't block indefinitely
debug('Waiting for specs to finish loading')
await Promise.race([
new Promise((resolve) => setTimeout(resolve, 2000)),
specChangeSubscription.next(),
])

// Close out subscription
await specChangeSubscription.return(undefined)
} else {
debug('Already in %s testing mode', targetTestingType)
}

// This accounts for an edge case where a testing type has been previously opened, but
// the user then backs out to the testing type selector in launchpad. In that scenario,
// the testingType switch logic above does not trigger the browser to open, so we do it
// manually here
if (this.ctx.coreData.app.browserStatus === 'closed') {
debug('No browser instance, launching...')
await this.ctx.lifecycleManager.setInitialActiveBrowser()

await this.api.launchProject(
this.ctx.coreData.activeBrowser!,
{
name: '',
absolute: '',
relative: '',
specType: targetTestingType === 'e2e' ? 'integration' : 'component',
},
)

debug('Browser launched')
} else {
debug(`Browser already running, status ${this.ctx.coreData.app.browserStatus}`)

if (this.ctx.coreData.app.browserStatus !== 'open') {
await waitForBrowserToOpen()
}
}

// Now that we're in the correct testingType, verify the requested spec actually exists
// We don't have specs available until a testingType is loaded, so even through we validated
// a matching file exists above it may not end up loading as a valid spec so we validate that here
const spec = this.ctx.project.getCurrentSpecByAbsolute(absoluteSpecPath)

if (!spec) {
throw new RunSpecError('SPEC_NOT_FOUND', `Unable to find matching spec with path ${absoluteSpecPath}`)
}

const browser = this.ctx.coreData.activeBrowser!

// Hooray, everything looks good and we're all set up
// Try to launch the requested spec by navigating to it in the browser
await this.api.runSpec(spec)

return {
testingType: targetTestingType,
browser,
spec,
}
} catch (err) {
if (!(err instanceof RunSpecError)) {
debug('Unexpected error during `runSpec` %o', err)
}

return {
code: err instanceof RunSpecError ? err.code : 'GENERAL_ERROR',
detailMessage: err.message,
}
}
}
}
2 changes: 1 addition & 1 deletion packages/data-context/src/data/ProjectLifecycleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ export class ProjectLifecycleManager {
* this sources the config from the various config sources
*/
async getFullInitialConfig (options: Partial<AllModeOptions> = this.ctx.modeOptions, withBrowsers = true): Promise<FullConfig> {
assert(this._configManager, 'Cannot get full config a config manager')
assert(this._configManager, 'Cannot get full config without a config manager')

return this._configManager.getFullInitialConfig(options, withBrowsers)
}
Expand Down
Loading

5 comments on commit af2f83c

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on af2f83c May 24, 2023

Choose a reason for hiding this comment

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

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.13.1/linux-arm64/update-v8-snapshot-cache-on-develop-af2f83c11b17ff057b1702e510f84a3454c68165/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on af2f83c May 24, 2023

Choose a reason for hiding this comment

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

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.13.1/linux-x64/update-v8-snapshot-cache-on-develop-af2f83c11b17ff057b1702e510f84a3454c68165/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on af2f83c May 24, 2023

Choose a reason for hiding this comment

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

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.13.1/darwin-arm64/update-v8-snapshot-cache-on-develop-af2f83c11b17ff057b1702e510f84a3454c68165/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on af2f83c May 24, 2023

Choose a reason for hiding this comment

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

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.13.1/darwin-x64/update-v8-snapshot-cache-on-develop-af2f83c11b17ff057b1702e510f84a3454c68165/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on af2f83c May 24, 2023

Choose a reason for hiding this comment

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

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.13.1/win32-x64/update-v8-snapshot-cache-on-develop-af2f83c11b17ff057b1702e510f84a3454c68165/cypress.tgz

Please sign in to comment.