Skip to content

Commit

Permalink
fix: restart dev-server on config change (#21212)
Browse files Browse the repository at this point in the history
* fix: restart dev-server on config change

* close dev server before cp is spawned

* fix test that is failing... not sure why

* cleanup unsued close events

* remove wait

* add back in close for unit tests

Co-authored-by: Tim Griesser <tgriesser10@gmail.com>
  • Loading branch information
ZachJW34 and tgriesser authored May 2, 2022
1 parent 1289b01 commit 00a0f5a
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 194 deletions.
1 change: 1 addition & 0 deletions npm/vite-dev-server/src/devServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function devServer (config: ViteDevServerConfig): Promise<Cypress.R

return {
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)
},
Expand Down
4 changes: 3 additions & 1 deletion npm/webpack-dev-server/src/devServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise<Cyp

resolve({
port,
// Close is for unit testing only. We kill this child process which will handle the closing of the server
close: (done) => {
srv.close((err) => {
if (err) {
Expand All @@ -101,7 +102,8 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise<Cyp

resolve({
port: result.server.options.port as number,
close: (done) => {
// Close is for unit testing only. We kill this child process which will handle the closing of the server
close: async (done) => {
debug('closing dev server')
result.server.stop().then(() => done?.()).catch(done).finally(() => {
debug('closed dev server')
Expand Down
262 changes: 149 additions & 113 deletions packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,156 +4,192 @@ import { getPathForPlatform } from '../../src/paths'
import { snapshotAUTPanel } from './support/snapshot-aut-panel'

describe('Cypress In Cypress CT', { viewportWidth: 1500, defaultCommandTimeout: 10000 }, () => {
beforeEach(() => {
cy.scaffoldProject('cypress-in-cypress')
cy.findBrowsers()
cy.openProject('cypress-in-cypress')
cy.startAppServer('component')
})

it('test component', () => {
cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.location().should((location) => {
expect(location.hash).to.contain('TestComponent.spec')
context('default config', () => {
beforeEach(() => {
cy.scaffoldProject('cypress-in-cypress')
cy.findBrowsers()
cy.openProject('cypress-in-cypress')
cy.startAppServer('component')
})

cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')

cy.findByTestId('aut-url').should('not.exist')
cy.findByTestId('select-browser').click()

cy.contains('Canary').should('be.visible')
cy.findByTestId('viewport').click()
it('test component', () => {
cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.location().should((location) => {
expect(location.hash).to.contain('TestComponent.spec')
})

snapshotAUTPanel('browsers open')
cy.contains('Canary').should('be.hidden')
cy.contains('The viewport determines the width and height of your application. By default the viewport will be 500px by 500px for Component Testing unless specified by a cy.viewport command.')
.should('be.visible')
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')

snapshotAUTPanel('viewport info open')
cy.findByTestId('aut-url').should('not.exist')
cy.findByTestId('select-browser').click()

cy.get('body').click()
cy.contains('Canary').should('be.visible')
cy.findByTestId('viewport').click()

cy.findByTestId('playground-activator').click()
cy.findByTestId('playground-selector').clear().type('[data-cy-root]')
snapshotAUTPanel('browsers open')
cy.contains('Canary').should('be.hidden')
cy.contains('The viewport determines the width and height of your application. By default the viewport will be 500px by 500px for Component Testing unless specified by a cy.viewport command.')
.should('be.visible')

snapshotAUTPanel('cy.get selector')
snapshotAUTPanel('viewport info open')

cy.findByTestId('playground-num-elements').contains('1 Match')
cy.get('body').click()

cy.window().then((win) => cy.spy(win.console, 'log'))
cy.findByTestId('playground-print').click().window().then((win) => {
expect(win.console.log).to.have.been.calledWith('%cCommand: ', 'font-weight: bold', 'cy.get(\'[data-cy-root]\')')
})
cy.findByTestId('playground-activator').click()
cy.findByTestId('playground-selector').clear().type('[data-cy-root]')

cy.findByLabelText('Selector Methods').click()
cy.findByRole('menuitem', { name: 'cy.contains' }).click()
snapshotAUTPanel('cy.get selector')

cy.findByTestId('playground-selector').clear().type('Component Test')
cy.findByTestId('playground-num-elements').contains('1 Match')

snapshotAUTPanel('cy.contains selector')
cy.window().then((win) => cy.spy(win.console, 'log'))
cy.findByTestId('playground-print').click().window().then((win) => {
expect(win.console.log).to.have.been.calledWith('%cCommand: ', 'font-weight: bold', 'cy.get(\'[data-cy-root]\')')
})

cy.findByTestId('playground-num-elements').contains('1 Match')
})
cy.findByLabelText('Selector Methods').click()
cy.findByRole('menuitem', { name: 'cy.contains' }).click()

it('navigation between specs and other parts of the app works', () => {
cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')

// go to Settings page and back to spec runner
cy.contains('a', 'Settings').click()
cy.contains(defaultMessages.settingsPage.device.title).should('be.visible')
cy.contains('a', 'Specs').click()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')

// go to Runs page and back to spec runner
cy.contains('a', 'Runs').click()
cy.contains(defaultMessages.runs.connect.title).should('be.visible')
cy.contains('a', 'Specs').click()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')
})
cy.findByTestId('playground-selector').clear().type('Component Test')

it('redirects to the specs list with error if a spec is not found', () => {
cy.visitApp()
const { noSpecErrorTitle, noSpecErrorIntro, noSpecErrorExplainer } = defaultMessages.specPage
const badFilePath = 'src/DoesNotExist.spec.js'

cy.visitApp(`/specs/runner?file=${badFilePath}`)
cy.contains(noSpecErrorTitle).should('be.visible')
cy.contains(noSpecErrorIntro).should('be.visible')
cy.contains(noSpecErrorExplainer).should('be.visible')
cy.contains(getPathForPlatform(badFilePath)).should('be.visible')
cy.location()
.its('href')
.should('eq', 'http://localhost:4455/__/#/specs')

// should clear after reload
cy.reload()
cy.contains(noSpecErrorTitle).should('not.exist')
})
snapshotAUTPanel('cy.contains selector')

it('redirects to the specs list with error if an open spec is not found when specs list updates', () => {
const { noSpecErrorTitle, noSpecErrorIntro, noSpecErrorExplainer } = defaultMessages.specPage

const goodFilePath = 'src/TestComponent.spec.jsx'
cy.findByTestId('playground-num-elements').contains('1 Match')
})

cy.visitApp(`/specs/runner?file=${goodFilePath}`)
it('navigation between specs and other parts of the app works', () => {
cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')

// go to Settings page and back to spec runner
cy.contains('a', 'Settings').click()
cy.contains(defaultMessages.settingsPage.device.title).should('be.visible')
cy.contains('a', 'Specs').click()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')

// go to Runs page and back to spec runner
cy.contains('a', 'Runs').click()
cy.contains(defaultMessages.runs.connect.title).should('be.visible')
cy.contains('a', 'Specs').click()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')
})

cy.contains('renders the test component').should('be.visible')
it('redirects to the specs list with error if a spec is not found', () => {
cy.visitApp()
const { noSpecErrorTitle, noSpecErrorIntro, noSpecErrorExplainer } = defaultMessages.specPage
const badFilePath = 'src/DoesNotExist.spec.js'

cy.withCtx((ctx, o) => {
ctx.actions.project.setSpecs(ctx.project.specs.filter((spec) => !spec.absolute.includes(o.path)))
}, { path: goodFilePath }).then(() => {
cy.visitApp(`/specs/runner?file=${badFilePath}`)
cy.contains(noSpecErrorTitle).should('be.visible')
cy.contains(noSpecErrorIntro).should('be.visible')
cy.contains(noSpecErrorExplainer).should('be.visible')
cy.contains(getPathForPlatform(goodFilePath)).should('be.visible')
cy.contains(getPathForPlatform(badFilePath)).should('be.visible')
cy.location()
.its('href')
.should('eq', 'http://localhost:4455/__/#/specs')

// should clear after reload
cy.reload()
cy.contains(noSpecErrorTitle).should('not.exist')
})
})

it('browser picker in runner calls mutation with current spec path', () => {
cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')
it('redirects to the specs list with error if an open spec is not found when specs list updates', () => {
const { noSpecErrorTitle, noSpecErrorIntro, noSpecErrorExplainer } = defaultMessages.specPage

const goodFilePath = 'src/TestComponent.spec.jsx'

cy.visitApp(`/specs/runner?file=${goodFilePath}`)

cy.withCtx((ctx, o) => {
o.sinon.stub(ctx.actions.app, 'setActiveBrowserById')
o.sinon.stub(ctx.actions.project, 'launchProject').resolves()
cy.contains('renders the test component').should('be.visible')

cy.withCtx((ctx, o) => {
ctx.actions.project.setSpecs(ctx.project.specs.filter((spec) => !spec.absolute.includes(o.path)))
}, { path: goodFilePath }).then(() => {
cy.contains(noSpecErrorTitle).should('be.visible')
cy.contains(noSpecErrorIntro).should('be.visible')
cy.contains(noSpecErrorExplainer).should('be.visible')
cy.contains(getPathForPlatform(goodFilePath)).should('be.visible')
cy.location()
.its('href')
.should('eq', 'http://localhost:4455/__/#/specs')
})
})

it('browser picker in runner calls mutation with current spec path', () => {
cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')

cy.withCtx((ctx, o) => {
o.sinon.stub(ctx.actions.app, 'setActiveBrowserById')
o.sinon.stub(ctx.actions.project, 'launchProject').resolves()
})

cy.get('[data-cy="select-browser"]')
.click()

cy.contains('Firefox')
.click()

cy.withCtx((ctx, o) => {
const browserId = (ctx.actions.app.setActiveBrowserById as SinonStub).args[0][0]
const genId = ctx.fromId(browserId, 'Browser')

expect(ctx.actions.app.setActiveBrowserById).to.have.been.calledWith(browserId)
expect(genId).to.eql('firefox-firefox-stable')
expect(ctx.actions.project.launchProject).to.have.been.calledWith(
ctx.coreData.currentTestingType, {}, o.sinon.match(new RegExp('cypress\-in\-cypress\/src\/TestComponent\.spec\.jsx$')),
)
})
})

cy.get('[data-cy="select-browser"]')
.click()
it('restarts dev server on config change', () => {
cy.visitApp()

cy.withCtx(async (ctx, { testState, sinon }) => {
sinon.stub(ctx._apis.projectApi.getDevServer(), 'close')
const devServerReady =
new Promise<void>((res) => {
ctx._apis.projectApi.getDevServer().emitter.on('dev-server:compile:success', () => res())
})

testState.originalCypressConfig = await ctx.file.readFileInProject('cypress.config.js')
const newCypressConfig = testState.originalCypressConfig.replace(`webpackConfig: require('./webpack.config.js')`, `webpackConfig: {}`)

await ctx.actions.file.writeFileInProject('cypress.config.js', newCypressConfig)
await devServerReady
})

cy.contains('Firefox')
.click()
cy.contains('TestComponent.spec').click()
cy.get('.failed > .num').should('contain', 1)

cy.withCtx((ctx, o) => {
const browserId = (ctx.actions.app.setActiveBrowserById as SinonStub).args[0][0]
const genId = ctx.fromId(browserId, 'Browser')
cy.withCtx(async (ctx, { testState }) => {
await ctx.actions.file.writeFileInProject('cypress.config.js', testState.originalCypressConfig)
})

expect(ctx.actions.app.setActiveBrowserById).to.have.been.calledWith(browserId)
expect(genId).to.eql('firefox-firefox-stable')
expect(ctx.actions.project.launchProject).to.have.been.calledWith(
ctx.coreData.currentTestingType, {}, o.sinon.match(new RegExp('cypress\-in\-cypress\/src\/TestComponent\.spec\.jsx$')),
)
cy.get('.passed > .num').should('contain', 1)
})
})

it('set the correct viewport values from CLI', () => {
cy.openProject('cypress-in-cypress', ['--config', 'viewportWidth=333,viewportHeight=333'])
cy.startAppServer('component')
context('custom config', () => {
beforeEach(() => {
cy.scaffoldProject('cypress-in-cypress')
cy.findBrowsers()
})

it('set the correct viewport values from CLI', () => {
cy.openProject('cypress-in-cypress', ['--config', 'viewportWidth=333,viewportHeight=333'])
cy.startAppServer('component')

cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.visitApp()
cy.contains('TestComponent.spec').click()

cy.get('#unified-runner').should('have.css', 'width', '333px')
cy.get('#unified-runner').should('have.css', 'height', '333px')
cy.get('#unified-runner').should('have.css', 'width', '333px')
cy.get('#unified-runner').should('have.css', 'height', '333px')
})
})
})
3 changes: 1 addition & 2 deletions packages/app/cypress/e2e/specs_list_actual_git_repo.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ describe('Spec List - Git Status', () => {
beforeEach(() => {
cy.scaffoldProject('cypress-in-cypress')
.then((projectPath) => {
cy.task('initGitRepoForTestProject', projectPath)
cy.wait(500)
cy.openProject('cypress-in-cypress')
cy.startAppServer('e2e')
cy.task('initGitRepoForTestProject', projectPath)
cy.visitApp()
})
})
Expand Down
7 changes: 5 additions & 2 deletions packages/data-context/src/actions/ProjectActions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CodeGenType, MutationSetProjectPreferencesArgs, NexusGenObjects, NexusGenUnions, TestingTypeEnum } from '@packages/graphql/src/gen/nxs.gen'
import type { InitializeProjectOptions, FoundBrowser, FoundSpec, LaunchOpts, OpenProjectLaunchOptions, Preferences, TestingType, ReceivedCypressOptions, AddProject } from '@packages/types'
import type { InitializeProjectOptions, FoundBrowser, FoundSpec, LaunchOpts, OpenProjectLaunchOptions, Preferences, TestingType, ReceivedCypressOptions, AddProject, FullConfig } from '@packages/types'
import execa from 'execa'
import path from 'path'
import assert from 'assert'
Expand Down Expand Up @@ -34,7 +34,10 @@ export interface ProjectApiShape {
getCurrentProjectSavedState(): {} | undefined
setPromptShown(slug: string): void
getDevServer (): {
updateSpecs: (specs: FoundSpec[]) => void
updateSpecs(specs: FoundSpec[]): void
start(options: {specs: Cypress.Spec[], config: FullConfig}): Promise<{port: number}>
close(): void
emitter: EventEmitter
}
isListening: (url: string) => Promise<void>
}
Expand Down
Loading

3 comments on commit 00a0f5a

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 00a0f5a May 2, 2022

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 platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.0.0/linux-x64/10.0-release-00a0f5a0e905fdfe5ef9f8ba71f915ed20ca4b72/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 00a0f5a May 2, 2022

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 platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.0.0/darwin-x64/10.0-release-00a0f5a0e905fdfe5ef9f8ba71f915ed20ca4b72/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 00a0f5a May 2, 2022

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 platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.0.0/win32-x64/10.0-release-00a0f5a0e905fdfe5ef9f8ba71f915ed20ca4b72/cypress.tgz

Please sign in to comment.