diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index a83d987c0b53..0285f5d67a85 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -12,6 +12,8 @@ _Released 07/05/2023 (PENDING)_ - Fixed issues where commands would fail with the error `must only be invoked from the spec file or support file`. Fixes [#27149](https://github.com/cypress-io/cypress/issues/27149). - Fixed an issue where chrome was not recovering from browser crashes properly. Fixes [#24650](https://github.com/cypress-io/cypress/issues/24650). - Fixed a race condition that was causing a GraphQL error to appear on the [Debug page](https://docs.cypress.io/guides/cloud/runs#Debug) when viewing a running Cypress Cloud build. Fixed in [#27134](https://github.com/cypress-io/cypress/pull/27134). +- Fixed a race condition in electron where the test window exiting prematurely during the browser launch process was causing the whole test run to fail. Addressed in [#27167](https://github.com/cypress-io/cypress/pull/27167). + **Dependency Updates:** diff --git a/packages/server/lib/browsers/electron.ts b/packages/server/lib/browsers/electron.ts index 9cc810a6a43e..0876ba921a9c 100644 --- a/packages/server/lib/browsers/electron.ts +++ b/packages/server/lib/browsers/electron.ts @@ -159,25 +159,39 @@ export = { } }, async onNewWindow (this: BrowserWindow, e, url) { - const _win = this + let _win: BrowserWindow | null = this - const child = await _this._launchChild(e, url, _win, projectRoot, state, options, automation) - - // close child on parent close - _win.on('close', () => { - if (!child.isDestroyed()) { - child.destroy() - } + _win.on('closed', () => { + // in some cases, the browser/test will close before _launchChild completes, leaving a destroyed/stale window object. + // in these cases, we want to proceed to the next test/open window without critically failing + _win = null }) - // add this pid to list of pids - tryToCall(child, () => { - if (instance && instance.pid) { - if (!instance.allPids) throw new Error('Missing allPids!') - - instance.allPids.push(child.webContents.getOSProcessId()) + try { + const child = await _this._launchChild(e, url, _win, projectRoot, state, options, automation) + + // close child on parent close + _win.on('close', () => { + if (!child.isDestroyed()) { + child.destroy() + } + }) + + // add this pid to list of pids + tryToCall(child, () => { + if (instance && instance.pid) { + if (!instance.allPids) throw new Error('Missing allPids!') + + instance.allPids.push(child.webContents.getOSProcessId()) + } + }) + } catch (e) { + if (_win === null) { + debug(`The window was closed while launching the child process. This usually means the browser or test errored before fully completing the launch process. Cypress will proceed to the next test`) + } else { + throw e } - }) + } }, } diff --git a/system-tests/__snapshots__/browser_crash_handling_spec.js b/system-tests/__snapshots__/browser_crash_handling_spec.js index 6d714856bc66..2195683ca8ef 100644 --- a/system-tests/__snapshots__/browser_crash_handling_spec.js +++ b/system-tests/__snapshots__/browser_crash_handling_spec.js @@ -403,3 +403,85 @@ This can happen for many different reasons: ` + +exports['Browser Crash Handling / when the window closes mid launch of the browser process / passes'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 2 found (abort_beforeunload_event_child.cy.ts, abort_beforeunload_event.cy.ts) │ + │ Searched: cypress/e2e/abort_beforeunload_event_child.cy.ts, cypress/e2e/abort_beforeunload_e │ + │ vent.cy.ts │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: abort_beforeunload_event_child.cy.ts (1 of 2) + + + ✓ will exit even if an beforeload event dialog is present in a child window + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: abort_beforeunload_event_child.cy.ts │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: abort_beforeunload_event.cy.ts (2 of 2) + + + ✓ will exit even if an beforeload event dialog is present + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: abort_beforeunload_event.cy.ts │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ abort_beforeunload_event_child.cy.t XX:XX 1 1 - - - │ + │ s │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ abort_beforeunload_event.cy.ts XX:XX 1 1 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 2 2 - - - + + +` diff --git a/system-tests/projects/e2e/blocking_beforeunload_event.html b/system-tests/projects/e2e/blocking_beforeunload_event.html new file mode 100644 index 000000000000..8adb88dd9978 --- /dev/null +++ b/system-tests/projects/e2e/blocking_beforeunload_event.html @@ -0,0 +1,9 @@ + + + diff --git a/system-tests/projects/e2e/cypress/e2e/abort_beforeunload_event.cy.ts b/system-tests/projects/e2e/cypress/e2e/abort_beforeunload_event.cy.ts new file mode 100644 index 000000000000..fca2d255be6e --- /dev/null +++ b/system-tests/projects/e2e/cypress/e2e/abort_beforeunload_event.cy.ts @@ -0,0 +1,3 @@ +it('will exit even if an beforeload event dialog is present', function () { + cy.visit('/blocking_beforeunload_event.html') +}) diff --git a/system-tests/projects/e2e/cypress/e2e/abort_beforeunload_event_child.cy.ts b/system-tests/projects/e2e/cypress/e2e/abort_beforeunload_event_child.cy.ts new file mode 100644 index 000000000000..f049c3fbb8f4 --- /dev/null +++ b/system-tests/projects/e2e/cypress/e2e/abort_beforeunload_event_child.cy.ts @@ -0,0 +1,3 @@ +it('will exit even if an beforeload event dialog is present in a child window', function () { + cy.window().invoke('open', '/blocking_beforeunload_event.html') +}) diff --git a/system-tests/test/browser_crash_handling_spec.js b/system-tests/test/browser_crash_handling_spec.js index 0fce49a5af1b..865a2bad1b4d 100644 --- a/system-tests/test/browser_crash_handling_spec.js +++ b/system-tests/test/browser_crash_handling_spec.js @@ -67,4 +67,16 @@ describe('Browser Crash Handling', () => { expectedExitCode: 1, }) }) + + context('when the window closes mid launch of the browser process', () => { + systemTests.it('passes', { + browser: 'electron', + spec: 'abort_beforeunload_event_child.cy.ts,abort_beforeunload_event.cy.ts', + snapshot: true, + expectedExitCode: 0, + config: { + video: false, + }, + }) + }) })