Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc: Close extra tabs/windows between tests #28204

Merged
merged 10 commits into from
Nov 16, 2023
4 changes: 4 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ _Released 11/7/2023 (PENDING)_
- Fixed an issue where network requests made from tabs/windows other than the main Cypress tab would be delayed. Fixes [#28113](https://github.com/cypress-io/cypress/issues/28113).
- Stopped processing CDP events at the end of a spec when Test Isolation is off and Test Replay is enabled. Addressed in [#28213](https://github.com/cypress-io/cypress/pull/28213).

**Misc:**

- Browser tabs and windows other than the Cypress tab are now closed between tests. Addressed in [#28204](https://github.com/cypress-io/cypress/pull/28204).

## 13.4.0

_Released 10/30/2023_
Expand Down
5 changes: 5 additions & 0 deletions packages/driver/src/cypress/cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
this.enqueue($Command.create(attrs))
})

// clears out any extra tabs/windows between tests
Cypress.on('test:before:run:async', () => {
return Cypress.backend('close:extra:targets')
})

handleCrossOriginCookies(Cypress)
}

Expand Down
12 changes: 12 additions & 0 deletions packages/server/lib/browsers/browser-cri-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,18 @@ export class BrowserCriClient {
this.extraTargetClients.delete(targetId)
}

async closeExtraTargets () {
for (const [targetId] of this.extraTargetClients) {
Copy link
Member

Choose a reason for hiding this comment

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

I think this for loop with the awaited promise will be blocking -- would it be better to map these and use Promise.all to ensure each completes if we do need to wait on these?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's intended to be blocking and will await all of them as-is. Using Promise.all would run them in parallel, but I'm not sure that's advisable with CDP. I think it's a safer bet to run them serially.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why wouldn't that be advisable with CDP? I think using Promise.all makes sense here as well.

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 thought there was a case where CDP had trouble with too many commands being run at once, but I can't find that instance. I updated it to run the target closing in parallel.

debug('Close extra target (id: %s)', targetId)

try {
await this.browserClient.send('Target.closeTarget', { targetId })
} catch (err: any) {
debug('Closing extra target errored: %s', err?.stack || err)
}
}
}

/**
* Closes the browser client socket as well as the socket for the currently attached page target
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/server/lib/browsers/chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,4 +637,8 @@ export = {
// with additional method to close the remote connection
return launchedBrowser
},

async closeExtraTargets () {
return browserCriClient?.closeExtraTargets()
},
}
4 changes: 4 additions & 0 deletions packages/server/lib/browsers/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,4 +535,8 @@ export = {

return instance
},

async closeExtraTargets () {
return browserCriClient?.closeExtraTargets()
},
}
4 changes: 4 additions & 0 deletions packages/server/lib/browsers/firefox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,3 +579,7 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc

return browserInstance
}

export async function closeExtraTargets () {
debug('Closing extra targets is not currently supported in Firefox')
}
11 changes: 11 additions & 0 deletions packages/server/lib/browsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,15 @@ export = {

return instance
},

/**
* Closes extra targets that are not the Cypress tab
*/
async closeExtraTargets () {
if (!instance || !instance.browser) return
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
if (!instance || !instance.browser) return
if (!instance?.browser) return

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 62e08a3


const browserLauncher = await getBrowserLauncher(instance.browser, [])

await browserLauncher.closeExtraTargets()
},
} as const
4 changes: 4 additions & 0 deletions packages/server/lib/browsers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export type BrowserLauncher = {
* Used to connect the protocol to an existing browser.
*/
connectProtocolToBrowser: (options: { protocolManager?: ProtocolManagerShape }) => Promise<void>
/**
* Closes any targets that are not the currently-attached Cypress target
*/
closeExtraTargets: () => Promise<void>
}

export type GracefulShutdownOptions = {
Expand Down
4 changes: 4 additions & 0 deletions packages/server/lib/browsers/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,7 @@ export async function open (browser: Browser, url: string, options: BrowserLaunc

return new WkInstance()
}

export async function closeExtraTargets () {
debug('Closing extra targets is not currently supported in Webkit')
}
5 changes: 5 additions & 0 deletions packages/server/lib/project-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ export class ProjectBase extends EE {
onFocusTests: options.onFocusTests,
onSpecChanged: options.onSpecChanged,
onSavedStateChanged: (state: any) => this.saveState(state),
closeExtraTargets: this.closeExtraTargets,

onCaptureVideoFrames: (data: any) => {
// TODO: move this to browser automation middleware
Expand Down Expand Up @@ -417,6 +418,10 @@ export class ProjectBase extends EE {
return this.server.socket.resetBrowserState()
}

closeExtraTargets () {
return browsers.closeExtraTargets()
}

isRunnerSocketConnected () {
return this.server.socket.isRunnerSocketConnected()
}
Expand Down
4 changes: 3 additions & 1 deletion packages/server/lib/socket-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export class SocketBase {
onSpecChanged () {},
onChromiumRun () {},
onReloadBrowser () {},
checkForAppErrors () {},
closeExtraTargets () {},
onSavedStateChanged () {},
onTestFileChange () {},
onCaptureVideoFrames () {},
Expand Down Expand Up @@ -478,6 +478,8 @@ export class SocketBase {
return (telemetry.exporter() as OTLPTraceExporterCloud)?.send(args[0], () => {}, (err) => {
debug('error exporting telemetry data from browser %s', err)
})
case 'close:extra:targets':
return options.closeExtraTargets()
default:
throw new Error(`You requested a backend event we cannot handle: ${eventName}`)
}
Expand Down
34 changes: 33 additions & 1 deletion packages/server/test/unit/browsers/browser-cri-client_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ describe('lib/browsers/cri-client', function () {
BrowserCriClient._onTargetDestroyed(options as any)

expect(options.browserCriClient.removeExtraTargetClient).to.be.calledWith('target-id')
// error is caught or else the test would fail
// error is caught or else the test would fail
})

it('removes the extra target client from the tracker', () => {
Expand Down Expand Up @@ -546,6 +546,38 @@ describe('lib/browsers/cri-client', function () {
})
})

context('#closeExtraTargets', () => {
it('closes any extra tracked targets', async () => {
const browserClient = await getClient() as any

browserClient.browserClient.send = sinon.stub().resolves()

browserClient.addExtraTargetClient({ targetId: 'target-id-1' }, {})
browserClient.addExtraTargetClient({ targetId: 'target-id-2' }, {})

await browserClient.closeExtraTargets()

expect(browserClient.browserClient.send).to.be.calledWith('Target.closeTarget', { targetId: 'target-id-1' })
expect(browserClient.browserClient.send).to.be.calledWith('Target.closeTarget', { targetId: 'target-id-2' })
})

it('ignores errors', async () => {
const browserClient = await getClient() as any

browserClient.browserClient.send = sinon.stub().resolves()
browserClient.browserClient.send.onFirstCall().rejects(new Error('failed to close target'))

browserClient.addExtraTargetClient({ targetId: 'target-id-1' }, {})
browserClient.addExtraTargetClient({ targetId: 'target-id-2' }, {})

await browserClient.closeExtraTargets()

expect(browserClient.browserClient.send).to.be.calledWith('Target.closeTarget', { targetId: 'target-id-1' })
expect(browserClient.browserClient.send).to.be.calledWith('Target.closeTarget', { targetId: 'target-id-2' })
// error is caught or else the test would fail
})
})

context('#close', function () {
it('closes the currently attached target if it exists and the browser client', async function () {
const mockCurrentlyAttachedTarget = {
Expand Down