Skip to content

Commit 30e48d1

Browse files
authored
chore: handle errors (#31854)
* chore: handle errors * Fix ts, add test * Fix error title * Fix ts * Fix ts
1 parent b4a663a commit 30e48d1

File tree

11 files changed

+178
-19
lines changed

11 files changed

+178
-19
lines changed

packages/driver/cypress.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const baseConfig: Cypress.ConfigOptions = {
4040
},
4141
component: {
4242
experimentalSingleTabRunMode: true,
43-
specPattern: 'cypress/component/**/*.cy.js',
43+
specPattern: 'cypress/component/**/*.cy.{js,ts}',
4444
supportFile: false,
4545
devServer: (devServerOptions) => {
4646
return cypressWebpackDevServer({

packages/driver/cypress/component/spec.cy.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,17 @@ describe('component testing', () => {
5454
expect(Cypress.log).to.be.calledWithMatch(sinon.match({ 'message': `Error: "Promise rejected with a string!"`, name: 'uncaught exception' }))
5555
})
5656
})
57+
58+
it('fails when trying to use cy.prompt in component tests', (done) => {
59+
cy.spy(Cypress, 'log').log(false)
60+
61+
cy.on('fail', (err) => {
62+
expect(err.message).to.include('`cy.prompt` is currently only supported in end-to-end tests.')
63+
64+
done()
65+
})
66+
67+
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
68+
cy.prompt('Hello, world!')
69+
})
5770
})

packages/driver/cypress/e2e/commands/prompt/prompt-initialization-error.cy.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
11
describe('src/cy/commands/prompt', () => {
2-
it('errors if wait for ready does not return success', (done) => {
2+
it('errors if wait for ready does not return success and error is ENOSPC', (done) => {
33
const backendStub = cy.stub(Cypress, 'backend').log(false)
44

5+
const error = new Error(`no space left on device, open '<stripped-path>bundle.tar`)
6+
7+
error.name = 'ENOSPC'
8+
9+
backendStub.callThrough()
10+
backendStub.withArgs('wait:for:cy:prompt:ready').resolves({ success: false, error })
11+
12+
cy.on('fail', (err) => {
13+
expect(err.message).to.include('Failed to download cy.prompt Cloud code')
14+
expect(err.message).to.include(`no space left on device, open '<stripped-path>bundle.tar`)
15+
16+
done()
17+
})
18+
19+
cy.visit('http://www.foobar.com:3500/fixtures/dom.html')
20+
21+
cy['commandFns']['prompt'].__reset()
22+
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
23+
cy.prompt('Hello, world!')
24+
})
25+
26+
it('errors if wait for ready does not return success and error is ECONNREFUSED', (done) => {
27+
const backendStub = cy.stub(Cypress, 'backend').log(false)
28+
29+
const error = new Error(`'<stripped-path>bundle.tar' timed out after 10000s`)
30+
31+
error.name = 'ECONNREFUSED'
32+
533
backendStub.callThrough()
6-
backendStub.withArgs('wait:for:cy:prompt:ready').resolves({ success: false })
34+
backendStub.withArgs('wait:for:cy:prompt:ready').resolves({ success: false, error })
735

836
cy.on('fail', (err) => {
9-
expect(err.message).to.include('error waiting for cy prompt bundle to be downloaded and ready')
37+
expect(err.message).to.include('Timed out waiting for cy.prompt Cloud code:')
38+
expect(err.message).to.include(`'<stripped-path>bundle.tar' timed out after 10000s`)
1039

1140
done()
1241
})

packages/driver/cypress/e2e/commands/prompt/prompt.cy.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,22 @@ describe('src/cy/commands/prompt', () => {
2121
cy.prompt('Hello, world!')
2222
})
2323
})
24+
25+
it('fails when trying to use cy.prompt in a browser that is not supported', (done) => {
26+
if (Cypress.isBrowser({ family: 'chromium' })) {
27+
done()
28+
29+
return
30+
}
31+
32+
cy.on('fail', (err) => {
33+
expect(err.message).to.include('`cy.prompt` is only supported in Chromium-based browsers.')
34+
35+
done()
36+
})
37+
38+
cy.visit('http://www.foobar.com:3500/fixtures/dom.html')
39+
// @ts-expect-error - this will not error when we actually release the experimentalPromptCommand flag
40+
cy.prompt('Hello, world!')
41+
})
2442
})

packages/driver/src/cy/commands/prompt/index.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { init, loadRemote } from '@module-federation/runtime'
22
import type { CypressInternal, CyPromptDriverDefaultShape } from './prompt-driver-types'
33
import type Emitter from 'component-emitter'
4+
import $errUtils from '../../../cypress/error_utils'
45

56
interface CyPromptDriver { default: CyPromptDriverDefaultShape }
67

@@ -15,9 +16,26 @@ declare global {
1516
let initializedModule: CyPromptDriverDefaultShape | null = null
1617
const initializeModule = async (Cypress: Cypress.Cypress): Promise<CyPromptDriverDefaultShape> => {
1718
// Wait for the cy prompt bundle to be downloaded and ready
18-
const { success } = await Cypress.backend('wait:for:cy:prompt:ready')
19+
const { success, error } = await Cypress.backend('wait:for:cy:prompt:ready')
20+
21+
if (error) {
22+
if (error.name === 'ENOSPC') {
23+
$errUtils.throwErrByPath('prompt.promptDownloadError', {
24+
args: {
25+
error,
26+
},
27+
})
28+
} else {
29+
$errUtils.throwErrByPath('prompt.promptDownloadTimedOut', {
30+
args: {
31+
error,
32+
},
33+
})
34+
}
35+
}
1936

20-
if (!success) {
37+
if (!success && !error) {
38+
// TODO: Generic error message
2139
throw new Error('error waiting for cy prompt bundle to be downloaded and ready')
2240
}
2341

@@ -40,6 +58,7 @@ const initializeModule = async (Cypress: Cypress.Cypress): Promise<CyPromptDrive
4058
const module = await loadRemote<CyPromptDriver>('cy-prompt')
4159

4260
if (!module?.default) {
61+
// TODO: Generic error message
4362
throw new Error('error loading cy prompt driver')
4463
}
4564

@@ -75,10 +94,18 @@ export default (Commands, Cypress, cy) => {
7594
}
7695

7796
const prompt = async (message: string, options: object = {}) => {
97+
if (Cypress.testingType === 'component') {
98+
$errUtils.throwErrByPath('prompt.promptTestingTypeError')
99+
100+
return
101+
}
102+
78103
if (!initializeCloudCyPromptPromise) {
79104
// TODO: (cy.prompt) We will look into supporting other browsers (and testing them)
80105
// as this is rolled out
81-
throw new Error('`cy.prompt()` is not supported in this browser.')
106+
$errUtils.throwErrByPath('prompt.promptSupportedBrowser')
107+
108+
return
82109
}
83110

84111
try {
@@ -92,6 +119,8 @@ export default (Commands, Cypress, cy) => {
92119

93120
return await cyPrompt(message, options)
94121
} catch (error) {
122+
// TODO: Check error that the user is logged in / record key
123+
95124
// TODO: handle this better
96125
throw new Error(`CyPromptDriver not found: ${error}`)
97126
}

packages/driver/src/cypress/error_messages.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,41 @@ export default {
13221322
`,
13231323
},
13241324

1325+
prompt: {
1326+
promptDownloadError (obj) {
1327+
return {
1328+
message: stripIndent`\
1329+
Failed to download cy.prompt Cloud code:
1330+
1331+
- ${obj.error.code}: ${obj.error.message}
1332+
1333+
Check your network connection and file settings to ensure download is not interrupted.
1334+
`,
1335+
docsUrl: 'https://on.cypress.io/prompt-download-error',
1336+
}
1337+
},
1338+
promptDownloadTimedOut (obj) {
1339+
return {
1340+
message: stripIndent`\
1341+
Timed out waiting for cy.prompt Cloud code:
1342+
1343+
- ${obj.error.code}: ${obj.error.message}
1344+
1345+
Check your network connection and system configuration.
1346+
`,
1347+
docsUrl: 'https://on.cypress.io/prompt-download-error',
1348+
}
1349+
},
1350+
promptSupportedBrowser: stripIndent`\
1351+
\`cy.prompt\` is only supported in Chromium-based browsers.
1352+
1353+
Use Chrome, Electron, Chromium, or Chrome for Testing.
1354+
`,
1355+
promptTestingTypeError: stripIndent`\
1356+
\`cy.prompt\` is currently only supported in end-to-end tests.
1357+
`,
1358+
},
1359+
13251360
proxy: {
13261361
js_rewriting_failed: stripIndent`\
13271362
An error occurred in the Cypress proxy layer while rewriting your source code. This is a bug in Cypress. Open an issue if you see this message.

packages/driver/types/internal-types-lite.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ declare namespace Cypress {
4242
(task: 'protocol:test:before:after:run:async', attributes: any, options: any): Promise<void>
4343
(task: 'protocol:url:changed', input: any): Promise<void>
4444
(task: 'protocol:page:loading', input: any): Promise<void>
45-
(task: 'wait:for:cy:prompt:ready'): Promise<{ success: boolean }>
45+
(task: 'wait:for:cy:prompt:ready'): Promise<{ success: boolean, error?: Error }>
4646
}
4747

4848
interface Devices {

packages/server/lib/cloud/cy-prompt/CyPromptLifecycleManager.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ const debug = Debug('cypress:server:cy-prompt-lifecycle-manager')
1818
export class CyPromptLifecycleManager {
1919
private static hashLoadingMap: Map<string, Promise<void>> = new Map()
2020
private static watcher: chokidar.FSWatcher | null = null
21-
private cyPromptManagerPromise?: Promise<CyPromptManager | null>
21+
private cyPromptManagerPromise?: Promise<{
22+
cyPromptManager?: CyPromptManager
23+
error?: Error
24+
}>
2225
private cyPromptManager?: CyPromptManager
2326
private listeners: ((cyPromptManager: CyPromptManager) => void)[] = []
2427

@@ -72,7 +75,7 @@ export class CyPromptLifecycleManager {
7275
// Clean up any registered listeners
7376
this.listeners = []
7477

75-
return null
78+
return { error }
7679
})
7780

7881
this.cyPromptManagerPromise = cyPromptManagerPromise
@@ -99,7 +102,7 @@ export class CyPromptLifecycleManager {
99102
}: {
100103
projectId?: string
101104
cloudDataSource: CloudDataSource
102-
}): Promise<CyPromptManager> {
105+
}): Promise<{ cyPromptManager?: CyPromptManager, error?: Error }> {
103106
let cyPromptHash: string
104107
let cyPromptPath: string
105108

@@ -155,7 +158,7 @@ export class CyPromptLifecycleManager {
155158
this.cyPromptManager = cyPromptManager
156159
this.callRegisteredListeners()
157160

158-
return cyPromptManager
161+
return { cyPromptManager }
159162
}
160163

161164
private callRegisteredListeners () {
@@ -204,7 +207,10 @@ export class CyPromptLifecycleManager {
204207
}).catch((error) => {
205208
debug('Error during reload of cy prompt manager: %o', error)
206209

207-
return null
210+
return {
211+
cyPromptManager: undefined,
212+
error: new Error('Error during reload of cy prompt manager'),
213+
}
208214
})
209215
})
210216
}

packages/server/lib/socket-base.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,10 +541,13 @@ export class SocketBase {
541541
return options.closeExtraTargets()
542542
case 'wait:for:cy:prompt:ready':
543543
return getCtx().coreData.cyPromptLifecycleManager?.getCyPrompt().then(async (cyPrompt) => {
544-
await options.onCyPromptReady(cyPrompt)
544+
if (cyPrompt.cyPromptManager) {
545+
await options.onCyPromptReady(cyPrompt.cyPromptManager)
546+
}
545547

546548
return {
547-
success: cyPrompt && cyPrompt.status === 'INITIALIZED',
549+
success: cyPrompt.cyPromptManager && cyPrompt.cyPromptManager.status === 'INITIALIZED',
550+
error: cyPrompt.error ? errors.cloneErr(cyPrompt.error) : undefined,
548551
}
549552
})
550553
default:

packages/server/test/unit/socket_spec.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -551,23 +551,29 @@ describe('lib/socket', () => {
551551
context('on(backend:request, wait:for:cy:prompt:ready)', () => {
552552
it('awaits cy prompt ready and returns true if cy prompt is ready', function (done) {
553553
const mockCyPrompt = {
554-
status: 'INITIALIZED',
554+
cyPromptManager: {
555+
status: 'INITIALIZED',
556+
},
557+
error: undefined,
555558
}
556559

557560
ctx.coreData.cyPromptLifecycleManager.getCyPrompt.resolves(mockCyPrompt)
558561

559562
return this.client.emit('backend:request', 'wait:for:cy:prompt:ready', (resp) => {
560563
expect(resp.response).to.deep.eq({ success: true })
561564

562-
expect(this.options.onCyPromptReady).to.be.calledWith(mockCyPrompt)
565+
expect(this.options.onCyPromptReady).to.be.calledWith(mockCyPrompt.cyPromptManager)
563566

564567
return done()
565568
})
566569
})
567570

568571
it('awaits cy prompt ready and returns false if cy prompt is not ready', function (done) {
569572
const mockCyPrompt = {
570-
status: 'NOT_INITIALIZED',
573+
cyPromptManager: {
574+
status: 'NOT_INITIALIZED',
575+
},
576+
error: undefined,
571577
}
572578

573579
ctx.coreData.cyPromptLifecycleManager.getCyPrompt.resolves(mockCyPrompt)
@@ -578,6 +584,23 @@ describe('lib/socket', () => {
578584
return done()
579585
})
580586
})
587+
588+
it('awaits cy prompt ready and returns error if cy prompt error is thrown', function (done) {
589+
const mockCyPrompt = {
590+
cyPromptManager: undefined,
591+
error: new Error('not loaded'),
592+
}
593+
594+
ctx.coreData.cyPromptLifecycleManager.getCyPrompt.resolves(mockCyPrompt)
595+
596+
return this.client.emit('backend:request', 'wait:for:cy:prompt:ready', (resp) => {
597+
expect(resp.response).to.deep.eq({
598+
error: errors.cloneErr(mockCyPrompt.error),
599+
})
600+
601+
return done()
602+
})
603+
})
581604
})
582605

583606
context('on(save:app:state)', () => {

0 commit comments

Comments
 (0)