Skip to content

Commit

Permalink
implement JIT component experiment [run ci]
Browse files Browse the repository at this point in the history
rework run mode to reuse dev server throughout run and just update entries (similar to open mode) to hopefully remedy memory leak [run ci]
  • Loading branch information
AtofStryker committed Aug 16, 2024
1 parent 66af8e6 commit 4871267
Show file tree
Hide file tree
Showing 53 changed files with 4,879 additions and 32 deletions.
11 changes: 5 additions & 6 deletions .circleci/workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ mainBuildFilters: &mainBuildFilters
- 'update-v8-snapshot-cache-on-develop'
- 'jquery-patch-remove-unload'
- 'publish-binary'
- 'chore/use_build_docker_file_for_centos7'
- 'feat/experimentalJustInTimeCompile'

# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
Expand All @@ -43,7 +43,7 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'fix/webpack-batteries-included-for-yarn-pnp', << pipeline.git.branch >> ]
- equal: [ 'feat/experimentalJustInTimeCompile', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand All @@ -54,8 +54,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'chore/use_build_docker_file_for_centos7', << pipeline.git.branch >> ]
- equal: [ 'jquery-patch-remove-unload', << pipeline.git.branch >> ]
- equal: [ 'feat/experimentalJustInTimeCompile', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand All @@ -78,7 +77,7 @@ windowsWorkflowFilters: &windows-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'fix/webpack-batteries-included-for-yarn-pnp', << pipeline.git.branch >> ]
- equal: [ 'feat/experimentalJustInTimeCompile', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand Down Expand Up @@ -154,7 +153,7 @@ commands:
name: Set environment variable to determine whether or not to persist artifacts
command: |
echo "Setting SHOULD_PERSIST_ARTIFACTS variable"
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "chore/use_build_docker_file_for_centos7" ]]; then
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "feat/experimentalJustInTimeCompile" ]]; then
export SHOULD_PERSIST_ARTIFACTS=true
fi' >> "$BASH_ENV"
# You must run `setup_should_persist_artifacts` command and be using bash before running this command
Expand Down
8 changes: 7 additions & 1 deletion cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 13.13.4
## 13.14.0

_Released 8/27/2024 (PENDING)_

**Features:**

- Added new
[`experimentalJustInTimeCompile`](/guides/references/experiments#Configuration)
configuration option for component testing. This option will only compile resources directly related to your spec, compiling them 'just-in-time' before spec execution. This should result in improved memory management and performance for component tests in `cypress open` and `cypress run` modes, in particular for large component testing suites. [`experimentalJustInTimeCompile`](/guides/references/experiments#Configuration) is currently supported for [`webpack`](https://www.npmjs.com/package/webpack) and [`vite`](https://www.npmjs.com/package/vite). Addresses [#29244](https://github.com/cypress-io/cypress/issues/29244).

**Dependency Updates:**

- Updated `detect-port` from `1.3.0` to `1.6.1`. Addressed in [#30038](https://github.com/cypress-io/cypress/pull/30038).
Expand Down
8 changes: 8 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3124,6 +3124,14 @@ declare namespace Cypress {
* @default null
*/
experimentalSkipDomainInjection: string[] | null
/**
* Allows for just-in-time compiling of a component test, which will only compile assets related to the component.
* This results in a smaller bundler under test, reducing resource constraints on a given machine. This option is recommended
* for users with large component testing projects and those who are running into webpack 'chunk load error' issues.
* Supported for vite and webpack. For component testing only.
* @see https://on.cypress.io/experiments#Configuration
*/
experimentalJustInTimeCompile: boolean
/**
* Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm.
* @default false
Expand Down
7 changes: 6 additions & 1 deletion npm/vite-dev-server/src/devServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ export async function devServer (config: ViteDevServerConfig): Promise<Cypress.R
port,
// Close is for unit testing only. We kill this child process which will handle the closing of the server
close (cb) {
return server.close().then(() => cb?.()).catch(cb)
debug('closing dev server')

return server.close().then(() => {
debug('closed dev server')
cb?.()
}).catch(cb)
},
}
}
Expand Down
1 change: 1 addition & 0 deletions npm/vite-dev-server/src/plugins/cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const Cypress = (
let loader = fs.readFileSync(INIT_FILEPATH, 'utf8')

devServerEvents.on('dev-server:specs:changed', (specs: Spec[]) => {
debug(`dev-server:secs:changed: ${specs.map((spec) => spec.relative)}`)
specsPathsSet = getSpecsPathsSet(specs)
})

Expand Down
4 changes: 3 additions & 1 deletion npm/vite-dev-server/src/resolveConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const createViteDevServerConfig = async (config: ViteDevServerConfig, vit
function makeCypressViteConfig (config: ViteDevServerConfig, vite: Vite): InlineConfig | InlineConfig {
const {
cypressConfig: {
experimentalJustInTimeCompile,
port,
projectRoot,
devServerPublicPathRoute,
Expand Down Expand Up @@ -128,7 +129,8 @@ function makeCypressViteConfig (config: ViteDevServerConfig, vite: Vite): Inline
port: vitePort,
host: '127.0.0.1',
// Disable file watching and HMR when executing tests in `run` mode
...(isTextTerminal
// if experimentalJustInTimeCompile is configured, we need to watch for file changes as the spec entries are going to be updated per test in run mode
...(isTextTerminal && !experimentalJustInTimeCompile
? { watch: { ignored: '**/*' }, hmr: false }
: {}),
},
Expand Down
35 changes: 35 additions & 0 deletions npm/vite-dev-server/test/resolveConfig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,40 @@ describe('resolveConfig', function () {
expect(viteConfig.server?.hmr).to.be.undefined
})
})

describe('experimentalJustInTimeCompile', () => {
let viteDevServerConfig: ViteDevServerConfig

beforeEach(async () => {
const projectRoot = await scaffoldSystemTestProject(`vite${version}-inspect`)

viteDevServerConfig = getViteDevServerConfig(projectRoot)
viteDevServerConfig.cypressConfig.experimentalJustInTimeCompile = true
})

describe('open mode', () => {
beforeEach(() => {
viteDevServerConfig.cypressConfig.isTextTerminal = false
})

it('enables hmr and watching', async () => {
const viteConfig = await createViteDevServerConfig(viteDevServerConfig, discoveredVite)

expect(viteConfig.server.watch).to.be.undefined
})
})

describe('run mode', () => {
beforeEach(() => {
viteDevServerConfig.cypressConfig.isTextTerminal = true
})

it('enables hmr and watching', async () => {
const viteConfig = await createViteDevServerConfig(viteDevServerConfig, discoveredVite)

expect(viteConfig.server.watch).to.be.undefined
})
})
})
})
})
2 changes: 1 addition & 1 deletion npm/webpack-dev-server/src/devServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise<Cyp
return reject(new Error(`Expected port ${result.server.options.port} to be a number`))
}

debug('Component testing webpack server 4 started on port %s', result.server.options.port)
debug(`Component testing webpack server ${result.version} started on port %s`, result.server.options.port)

resolve({
port: result.server.options.port as number,
Expand Down
4 changes: 3 additions & 1 deletion npm/webpack-dev-server/src/makeDefaultWebpackConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function makeCypressWebpackConfig (
const {
devServerConfig: {
cypressConfig: {
experimentalJustInTimeCompile,
port,
projectRoot,
devServerPublicPathRoute,
Expand Down Expand Up @@ -98,7 +99,8 @@ export function makeCypressWebpackConfig (
devtool: 'inline-source-map',
} as any

if (isRunMode) {
// if experimentalJustInTimeCompile is configured, we need to watch for file changes as the spec entries are going to be updated per test
if (isRunMode && !experimentalJustInTimeCompile) {
// Disable file watching when executing tests in `run` mode
finalConfig.watchOptions = {
ignored: '**/*',
Expand Down
64 changes: 64 additions & 0 deletions npm/webpack-dev-server/test/makeWebpackConfig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,68 @@ describe('makeWebpackConfig', () => {
})
})
})

describe('experimentalJustInTimeCompile', () => {
let devServerConfig: WebpackDevServerConfig

const WEBPACK_MATRIX: {
webpack: 4 | 5
wds: 3 | 4 | 5
}[] = [
{
webpack: 4,
wds: 3,
},
{
webpack: 4,
wds: 4,
},
{
webpack: 5,
wds: 4,
},
{
webpack: 5,
wds: 5,
},
]

beforeEach(() => {
devServerConfig = {
specs: [],
cypressConfig: {
projectRoot: '.',
devServerPublicPathRoute: '/test-public-path',
experimentalJustInTimeCompile: true,
baseUrl: null,
} as Cypress.PluginConfigOptions,
webpackConfig: {
entry: { main: 'src/index.js' },
},
devServerEvents: new EventEmitter(),
}
})

WEBPACK_MATRIX.forEach(({ webpack, wds }) => {
describe(`webpack: v${webpack} with webpack-dev-server v${wds}`, () => {
describe('run mode', () => {
beforeEach(() => {
devServerConfig.cypressConfig.isTextTerminal = true
})

it('enables watching', async () => {
const actual = await makeWebpackConfig({
devServerConfig,
sourceWebpackModulesResult: createModuleMatrixResult({
webpack,
webpackDevServer: wds,
}),
})

expect(actual.watchOptions?.ignored).to.be.undefined
})
})
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { fixtureDirs } from '@tooling/system-tests'

type ProjectDirs = typeof fixtureDirs

const EXPERIMENTAL_JIT_DIR: ProjectDirs[number] = 'experimental-JIT'

const PROJECTS: {bundler: 'vite' | 'webpack'}[] = [
{ bundler: 'vite' },
{ bundler: 'webpack' },
]

for (const { bundler } of PROJECTS) {
const PROJECT_NAME = `${EXPERIMENTAL_JIT_DIR}/${bundler}`

describe(`CT experimentalJustInTimeCompile: ${bundler}`, { viewportWidth: 1500, defaultCommandTimeout: 30000 }, () => {
const visitComponentSpecAndVerifyPass = (specNumber: number) => {
cy.contains(`Component-${specNumber}.cy.jsx`).click()
cy.waitForSpecToFinish(undefined)
cy.get('[aria-label="Stats"] .passed > .num').should('contain', '1')
cy.get('[aria-label="Stats"] .failed > .num').should('contain', '--')
}

beforeEach(() => {
// @ts-expect-error
cy.scaffoldProject(PROJECT_NAME)
cy.findBrowsers()
}),

it(`can run multiple CT tests without interruption`, () => {
// @ts-expect-error
cy.openProject(PROJECT_NAME, ['--component'])
cy.startAppServer('component')
cy.visitApp()
cy.specsPageIsVisible()
visitComponentSpecAndVerifyPass(2)
cy.get('[data-cy="sidebar-link-specs-page"]').click()
visitComponentSpecAndVerifyPass(1)
cy.get('[data-cy="sidebar-link-specs-page"]').click()
visitComponentSpecAndVerifyPass(3)
})
})
}
4 changes: 2 additions & 2 deletions packages/app/src/runner/event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ interface AddGlobalListenerOptions {
}

const driverToLocalAndReporterEvents = 'run:start run:end'.split(' ')
const driverToSocketEvents = 'backend:request automation:request mocha recorder:frame'.split(' ')
const driverToSocketEvents = 'backend:request automation:request mocha recorder:frame dev-server:on-spec-update'.split(' ')
const driverToLocalEvents = 'viewport:changed config stop url:changed page:loading visit:failed visit:blank cypress:in:cypress:runner:event'.split(' ')
const socketRerunEvents = 'runner:restart watched:file:changed'.split(' ')
const socketToDriverEvents = 'net:stubbing:event request:event script:error cross:origin:cookies'.split(' ')
const socketToDriverEvents = 'net:stubbing:event request:event script:error cross:origin:cookies dev-server:on-spec-updated'.split(' ')
const localToReporterEvents = 'reporter:log:add reporter:log:state:changed reporter:log:remove'.split(' ')

/**
Expand Down
19 changes: 19 additions & 0 deletions packages/app/src/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,18 @@ async function initialize () {
window.UnifiedRunner.MobX.runInAction(() => setupRunner())
}

async function updateDevServerWithSpec (spec: SpecFile) {
return new Promise<void>((resolve, _reject) => {
// currently, we don't have criteria to reject the promise
// as the dev-server can take a long time to compile, which is variable per user.
Cypress.once('dev-server:on-spec-updated', () => {
resolve()
})

Cypress.emit('dev-server:on-spec-update', spec)
})
}

/**
* This wraps all of the required interactions to run a spec.
* Here are the things that happen:
Expand Down Expand Up @@ -437,6 +449,13 @@ async function executeSpec (spec: SpecFile, isRerun: boolean = false) {
}

if (window.__CYPRESS_TESTING_TYPE__ === 'component') {
if (config.experimentalJustInTimeCompile && !config.isTextTerminal) {
// If running with experimentalJustInTimeCompile enabled and in open mode,
// send a signal to the dev server to load the spec before running
// since the spec and related resources are not yet compiled.
await updateDevServerWithSpec(spec)
}

return runSpecCT(config, spec)
}

Expand Down
3 changes: 3 additions & 0 deletions packages/config/__snapshots__/index.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'experimentalMemoryManagement': false,
'experimentalModifyObstructiveThirdPartyCode': false,
'experimentalSkipDomainInjection': null,
'experimentalJustInTimeCompile': false,
'experimentalOriginDependencies': false,
'experimentalSourceRewriting': false,
'experimentalSingleTabRunMode': false,
Expand Down Expand Up @@ -130,6 +131,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'experimentalMemoryManagement': false,
'experimentalModifyObstructiveThirdPartyCode': false,
'experimentalSkipDomainInjection': null,
'experimentalJustInTimeCompile': false,
'experimentalOriginDependencies': false,
'experimentalSourceRewriting': false,
'experimentalSingleTabRunMode': false,
Expand Down Expand Up @@ -216,6 +218,7 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
'experimentalMemoryManagement',
'experimentalModifyObstructiveThirdPartyCode',
'experimentalSkipDomainInjection',
'experimentalJustInTimeCompile',
'experimentalOriginDependencies',
'experimentalSourceRewriting',
'experimentalSingleTabRunMode',
Expand Down
Loading

5 comments on commit 4871267

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4871267 Aug 16, 2024

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/13.13.4/linux-x64/feat/experimentalJustInTimeCompile-4871267b920c6019406ae871c9a551466446d815/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4871267 Aug 16, 2024

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/13.13.4/linux-arm64/feat/experimentalJustInTimeCompile-4871267b920c6019406ae871c9a551466446d815/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4871267 Aug 16, 2024

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/13.13.4/darwin-arm64/feat/experimentalJustInTimeCompile-4871267b920c6019406ae871c9a551466446d815/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4871267 Aug 16, 2024

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/13.13.4/darwin-x64/feat/experimentalJustInTimeCompile-4871267b920c6019406ae871c9a551466446d815/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4871267 Aug 16, 2024

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/13.13.4/win32-x64/feat/experimentalJustInTimeCompile-4871267b920c6019406ae871c9a551466446d815/cypress.tgz

Please sign in to comment.