Skip to content
Merged
83 changes: 83 additions & 0 deletions packages/app/cypress/e2e/studio/studio.cy.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

  • AUT URL getting replaced with "Enter URL" when tests are running and the "new test" view is open in the studio panel
  • The spec re-runs when the user closes the studio panel from the "new test" view

We should add a couple tests to verify this new behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added tests for both of these 👍🏻

Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,27 @@ describe('studio functionality', () => {
cy.location().its('hash').and('not.contain', 'testId=').and('not.contain', 'studio=')
})

it('does not prompt for URL when creating a new test in studio', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
it('does not prompt for URL when creating a new test in studio', () => {
it('does not prompt for URL when creating a new test in studio that already visits', () => {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I need to re-word this a different way. The issue was that when the user opens the studio panel to "new test" mode, we were already prompting for a URL in the address bar, even when the spec file was currently running and the AUT was at a URL already.

We only want to prompt for a URL once the test is actually created and it doesn't visit anything yet

launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })
cy.location().its('hash').should('contain', 'suiteId=r2').and('contain', 'studio=')
cy.waitForSpecToFinish()

cy.findByTestId('aut-url-input').should('have.value', 'http://localhost:4455/cypress/e2e/index.html')
})

it('does not reload the page if we didnt open a test in studio', () => {
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })

// set a property on the window to see if the page reloads
cy.window().then((w) => w['beforeReload'] = true)

// close new test mode
cy.findByTestId('studio-header-studio-button').click()

// if this property is still set on the window, then the page didn't reload
cy.window().then((w) => expect(w['beforeReload']).to.be.true)
})

it('removes the studio url parameters when closing studio new test', () => {
launchStudio({ specName: 'spec-w-visit.cy.js', createNewTestFromSuite: true })

Expand All @@ -747,6 +768,68 @@ describe('studio functionality', () => {
cy.location().its('hash').and('not.contain', 'suiteId=').and('not.contain', 'studio=')
})

it('stays in new test mode when studio panel is opened when the spec is running', () => {
loadProjectAndRunSpec()

cy.waitForSpecToFinish()

cy.findByTestId('studio-button').click()
cy.findByTestId('studio-panel').should('be.visible')
cy.findByTestId('new-test-button').should('be.visible')

// Verify we're initially in new test mode
cy.location().its('hash').should('contain', 'suiteId=r1').and('not.contain', 'testId=')

// Now restart the spec, which will call interceptTest with the running test
// This is where the bug would manifest - it would incorrectly switch from
// "new test" mode to "edit the running test" mode
cy.get('button.restart').click()

cy.get('.test').should('have.length', 1)
cy.get('.test').first().should('have.class', 'runnable-active')

// verify we're still in new test mode
cy.findByTestId('studio-panel').should('be.visible')
cy.findByTestId('new-test-button').should('be.visible')

// these should not exist if we stayed in new test mode
cy.findByTestId('studio-single-test-title').should('not.exist')
cy.findByTestId('record-button-recording').should('not.exist')

// verify URL still shows suite mode, not edit test mode
cy.location().its('hash').should('contain', 'suiteId=r1').and('not.contain', 'testId=')
})

it('shows test body sections correctly when studio panel is open and page is refreshed', () => {
loadProjectAndRunSpec()

cy.waitForSpecToFinish()

cy.findByTestId('studio-button').click()
cy.findByTestId('studio-panel').should('be.visible')
cy.findByTestId('new-test-button').should('be.visible')

cy.reload()

cy.waitForSpecToFinish()

cy.findByTestId('studio-panel').should('be.visible')
cy.findByTestId('new-test-button').should('be.visible')

// verify test body section is visible after refresh
cy.get('.runnable-instruments').should('be.visible')
cy.get('.runnable-commands-region').should('be.visible')

// verify the test body hook is present
cy.get('.hook-item').contains('test body').should('be.visible')

// verify commands are visible within the test body
cy.get('.command-name-visit').should('be.visible')

// Verify URL parameters show suite mode, not test mode
cy.location().its('hash').should('contain', 'suiteId=r1').and('not.contain', 'testId=')
})

describe('prompt for a new url', () => {
const autUrl = 'http://localhost:4455/cypress/e2e/index.html'
const visitUrl = 'cypress/e2e/index.html'
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/runner/SpecRunnerHeaderOpenMode.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ describe('SpecRunnerHeaderOpenMode', { viewportHeight: 500 }, () => {
// This emulates the 'needsUrl' state in the studio store
studioStore.setActive(true)
studioStore.setUrl(undefined)
studioStore._hasStarted = true

cy.mountFragment(SpecRunnerHeaderFragmentDoc, {
render: (gqlVal) => {
Expand Down
28 changes: 19 additions & 9 deletions packages/app/src/runner/event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,16 +307,25 @@ export class EventManager {
studioInitSuite({ suiteId })
})

const maybeCleanUpProtocol = () => {
const needsReload = this.studioStore.needsProtocolCleanup()

this.studioStore.cancel()

// only reload the page if Studio has actually been used for recording
if (needsReload) {
window.location.reload()
}
}

this.reporterBus.on('studio:cancel', () => {
this.ws.emit('studio:destroy', ({ error }) => {
if (error) {
// eslint-disable-next-line no-console
console.error(error)
}

this.studioStore.cancel()
// Reloading for now. This is the easiest way to clear out the protocol code from the front end
window.location.reload()
maybeCleanUpProtocol()
})
})

Expand Down Expand Up @@ -366,9 +375,7 @@ export class EventManager {
console.error(error)
}

this.studioStore.cancel()
// Reloading for now. This is the easiest way to clear out the protocol code from the front end
window.location.reload()
maybeCleanUpProtocol()
})
})

Expand Down Expand Up @@ -857,7 +864,8 @@ export class EventManager {
performance.measure('run', 'run-s', 'run-e')
})

const hasRunnableId = !!this.studioStore.testId || !!this.studioStore.suiteId
const hasActiveStudio = !!this.studioStore.testId ||
!!this.studioStore.newTestLineNumber

const studioSingleTestActive = this.studioStore.newTestLineNumber != null || !!this.studioStore.testId

Expand All @@ -869,7 +877,7 @@ export class EventManager {
autoScrollingEnabled: runState.autoScrollingEnabled,
isSpecsListOpen: runState.isSpecsListOpen,
scrollTop: runState.scrollTop,
studioActive: hasRunnableId,
studioActive: hasActiveStudio,
studioSingleTestActive,
} as ReporterStartInfo)
}
Expand Down Expand Up @@ -928,7 +936,9 @@ export class EventManager {
}

_interceptStudio (displayProps) {
if (this.studioStore.isActive) {
// Only intercept logs when Studio is actually recording a specific test
// Don't intercept when Studio is just open in "new test" mode
if (this.studioStore.isActive && this.studioStore.testId) {
Comment on lines +939 to +941
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this function still needed? We don't even display logs anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah this still gets called - we don't display anything but it seems like if the logs are intercepted here, they don't get displayed in the command log. So I need to update this because otherwise when we refresh the page and studio is open to "new test", the test body section doesn't get displayed in the left panel.

displayProps.hookId = this.studioStore.hookId

if (displayProps.name === 'visit' && displayProps.state === 'failed') {
Expand Down
9 changes: 7 additions & 2 deletions packages/app/src/store/studio-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ export const useStudioStore = defineStore('studioRecorder', {
this.newTestLineNumber = undefined
},

needsProtocolCleanup () {
// Protocol cleanup (page reload) is only needed if Studio has actually been used for recording
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this comment is accurate anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is... we only want to refresh the page when studio closes if we've actually entered recording mode (like, we've made it past the "new test" screen and actually created a test or opened an existing test in studio)

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, so by recording you just mean entering single test mode not that the user actually recorded anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, exactly - I'll update the comment to make that more clear

return this._hasStarted || this.testId || this._isStudioCreatedTest
},

openInstructionModal () {
this.instructionModalIsOpen = true
},
Expand Down Expand Up @@ -246,7 +251,7 @@ export const useStudioStore = defineStore('studioRecorder', {

interceptTest (test) {
// if this test is the one we created, we can just set the test id
if ((this.newTestLineNumber && test.invocationDetails?.line === this.newTestLineNumber) || this.suiteId) {
if ((this.newTestLineNumber && test.invocationDetails?.line === this.newTestLineNumber) || (this.suiteId && this._hasStarted)) {
this._isStudioCreatedTest = true
this.setTestId(test.id)
getCypress().runner.setIsStudioCreatedTest(true)
Expand Down Expand Up @@ -848,7 +853,7 @@ export const useStudioStore = defineStore('studioRecorder', {
},

needsUrl: (state) => {
return state.isActive && !state.url && !state.isFailed
return state.isActive && !state.url && !state.isFailed && state._hasStarted
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was causing the AUT URL to get removed whenever the panel was open at all, even if it was just the new test screen

},

testError: (state) => {
Expand Down
Loading