Skip to content

Commit d369dd5

Browse files
committed
misc: add automation command to cdp for forthcoming tab key
1 parent a15cd45 commit d369dd5

File tree

10 files changed

+121
-22
lines changed

10 files changed

+121
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
describe('__placeholder__/commands/actions/press', () => {
2+
it('dispatches the tab keypress to the AUT', () => {
3+
cy.visit('/fixtures/input_events.html')
4+
cy.get('#focus').focus().then(() => {
5+
return Cypress.automation('key:press', { key: 'Tab' })
6+
})
7+
8+
cy.get('#keyup').should('have.value', 'Tab')
9+
10+
cy.get('#keydown').should('have.value', 'Tab')
11+
})
12+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
3+
<head>
4+
<title>Input Event Monitor</title>
5+
<script defer>
6+
['keydown', 'keyup'].forEach((ev) => {
7+
document.addEventListener(ev, (data) => {
8+
document.body.appendChild(document.createTextNode(`${ev}:${data.key}`))
9+
document.getElementById(ev).value = data.key
10+
})
11+
})
12+
</script>
13+
</head>
14+
15+
<body>
16+
<input type="text" id="focus" />
17+
<input type="text" id="keyup" />
18+
<input type="text" id="keydown" />
19+
</body>

packages/server/lib/automation/automation.ts

+4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ export class Automation {
130130
}
131131

132132
normalize<T extends keyof AutomationCommands> (message: T, data: AutomationCommands[T]['dataType'], automate?): Promise<AutomationCommands[T]['returnType']> {
133+
debug('normalize', message)
134+
133135
return Bluebird.try(() => {
134136
switch (message) {
135137
case 'take:screenshot':
@@ -184,6 +186,7 @@ export class Automation {
184186
}
185187

186188
async push<T extends keyof AutomationCommands> (message: T, data: AutomationCommands[T]['dataType']) {
189+
debug('push', message)
187190
const result = await this.normalize(message, data)
188191

189192
if (result) {
@@ -192,6 +195,7 @@ export class Automation {
192195
}
193196

194197
async request<T extends keyof AutomationCommands> (message: T, data: AutomationCommands[T]['dataType'], fn) {
198+
debug('request', message)
195199
// curry in the message + callback function
196200
// for obtaining the external automation data
197201
const automate = this.automationValve(message, fn)
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
1-
import type { KeyPressSupportedKeys } from '@packages/types'
1+
import type { KeyPressParams, KeyPressSupportedKeys } from '@packages/types'
22
import type { SendDebuggerCommand } from '../../browsers/cdp_automation'
3+
import Debug from 'debug'
4+
5+
const debug = Debug('cypress:server:automation:command:keypress')
36

47
interface KeyCodeLookup extends Record<KeyPressSupportedKeys, string> {}
58

69
export const CDP_KEYCODE: KeyCodeLookup = {
7-
'TAB': 'U+000009',
10+
'Tab': 'U+000009',
811
}
912

10-
/*
11-
const BIDI_KEYCODE: KeyCodeLookup = {
12-
'TAB': '\uE004',
13-
}
14-
*/
13+
export async function cdpKeyPress ({ key }: KeyPressParams, send: SendDebuggerCommand): Promise<void> {
14+
debug('cdp keypress', { key })
15+
if (!CDP_KEYCODE[key]) {
16+
throw new Error(`${key} is not supported by 'cy.press()'.`)
17+
}
1518

16-
export async function cdpKeyPress ({ key }: { key: KeyPressSupportedKeys }, send: SendDebuggerCommand): Promise<void> {
1719
const keyIdentifier = CDP_KEYCODE[key]
1820

19-
await send('Input.dispatchKeyEvent', {
20-
type: 'keyDown',
21-
keyIdentifier,
22-
})
21+
try {
22+
await send('Input.dispatchKeyEvent', {
23+
type: 'rawKeyDown',
24+
key,
25+
code: key,
26+
keyIdentifier,
27+
})
28+
29+
await new Promise((resolve) => setTimeout(resolve, 20))
2330

24-
await send('Input.dispatchKeyEvent', {
25-
type: 'keyUp',
26-
keyIdentifier,
27-
})
31+
await send('Input.dispatchKeyEvent', {
32+
type: 'keyUp',
33+
key,
34+
code: key,
35+
keyIdentifier,
36+
})
37+
} catch (e) {
38+
debug(e)
39+
throw e
40+
}
2841
}

packages/server/lib/browsers/cdp_automation.ts

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { CDPClient, ProtocolManagerShape, WriteVideoFrame, AutomationMiddle
1414
import type { Automation } from '../automation'
1515
import { cookieMatches, CyCookie, CyCookieFilter } from '../automation/util'
1616
import { DEFAULT_NETWORK_ENABLE_OPTIONS, CriClient } from './cri-client'
17+
import { cdpKeyPress } from '../automation/commands/key_press'
1718

1819
export type CdpCommand = keyof ProtocolMapping.Commands
1920

@@ -586,6 +587,8 @@ export class CdpAutomation implements CDPClient, AutomationMiddleware {
586587
return this.sendDebuggerCommandFn('Runtime.evaluate', { expression: 'performance.memory.jsHeapSizeLimit' })
587588
case 'collect:garbage':
588589
return this.sendDebuggerCommandFn('HeapProfiler.collectGarbage')
590+
case 'key:press':
591+
return cdpKeyPress(data, this.sendDebuggerCommandFn)
589592
default:
590593
throw new Error(`No automation handler registered for: '${message}'`)
591594
}

packages/server/lib/browsers/cri-client.ts

+1
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ export class CriClient implements ICriClient {
250250
params?: CmdParams<TCmd>,
251251
sessionId?: string,
252252
): Promise<ProtocolMapping.Commands[TCmd]['returnType']> => {
253+
debug('preparing to send command %s to target %s', command, this.targetId)
253254
if (this._crashed) {
254255
return Promise.reject(new Error(`${command} will not run as the target browser or tab CRI connection has crashed`))
255256
}

packages/server/lib/open_project.ts

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ export class OpenProject {
127127

128128
return data
129129
}
130+
131+
return
130132
},
131133
})
132134
}

packages/server/lib/socket-base.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ import { cookieJar, SameSiteContext, automationCookieToToughCookie, Serializable
1919
import runEvents from './plugins/run_events'
2020
import type { OTLPTraceExporterCloud } from '@packages/telemetry'
2121
import { telemetry } from '@packages/telemetry'
22-
import type { Automation, AutomationCommands } from './automation'
22+
import type { Automation } from './automation'
2323
// eslint-disable-next-line no-duplicate-imports
2424
import type { Socket } from '@packages/socket'
2525

26-
import type { RunState, CachedTestState, ProtocolManagerShape } from '@packages/types'
26+
import type { RunState, CachedTestState, ProtocolManagerShape, AutomationCommands } from '@packages/types'
2727
import memory from './browsers/memory'
2828
import { privilegedCommandsManager } from './privileged-commands/privileged-commands-manager'
2929

@@ -95,6 +95,7 @@ export class SocketBase {
9595
}
9696

9797
onAutomation (socket, message, data, id) {
98+
debug('onAutomation', message)
9899
// instead of throwing immediately here perhaps we need
99100
// to make this more resilient by automatically retrying
100101
// up to 1 second in the case where our automation room

packages/server/test/unit/automation/commands/key_press.spec.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,25 @@ describe('key:press automation command', () => {
1212
})
1313

1414
it('dispaches a keydown followed by a keyup event to the provided send fn with the tab keycode', async () => {
15-
await cdpKeyPress({ key: 'TAB' }, sendFn)
15+
await cdpKeyPress({ key: 'Tab' }, sendFn)
1616

1717
expect(sendFn).to.have.been.calledWith('Input.dispatchKeyEvent', {
1818
type: 'keyDown',
19-
keyIdentifier: CDP_KEYCODE.TAB,
19+
keyIdentifier: CDP_KEYCODE.Tab,
2020
})
2121

2222
expect(sendFn).to.have.been.calledWith('Input.dispatchKeyEvent', {
2323
type: 'keyUp',
24-
keyIdentifier: CDP_KEYCODE.TAB,
24+
keyIdentifier: CDP_KEYCODE.Tab,
25+
})
26+
})
27+
28+
describe('when supplied an invalid key', () => {
29+
it('errors', async () => {
30+
// typescript would keep this from happening, but it hasn't yet
31+
// been checked for correctness since being received by automation
32+
// @ts-expect-error
33+
await expect(cdpKeyPress({ key: 'foo' })).to.be.rejectedWith('foo is not supported by \'cy.press()\'.')
2534
})
2635
})
2736
})

packages/types/src/server.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,42 @@ export interface LaunchArgs {
5858

5959
type NullableMiddlewareHook = ((message: unknown, data: unknown) => void) | null
6060

61-
export type OnRequestEvent = (eventName: string, data: any) => void
61+
export type KeyPressSupportedKeys = 'Tab'
62+
63+
interface CommandSignature<P = any, R = any> {
64+
dataType: P
65+
returnType: R
66+
}
67+
68+
export interface KeyPressParams {
69+
key: KeyPressSupportedKeys
70+
}
71+
72+
export interface AutomationCommands {
73+
'take:screenshot': CommandSignature
74+
'get:cookies': CommandSignature
75+
'get:cookie': CommandSignature
76+
'set:cookie': CommandSignature
77+
'set:cookies': CommandSignature
78+
'add:cookies': CommandSignature
79+
'clear:cookies': CommandSignature
80+
'clear:cookie': CommandSignature
81+
'change:cookie': CommandSignature
82+
'create:download': CommandSignature
83+
'canceled:download': CommandSignature
84+
'complete:download': CommandSignature
85+
'get:heap:size:limit': CommandSignature
86+
'collect:garbage': CommandSignature
87+
'reset:browser:tabs:for:next:spec': CommandSignature
88+
'reset:browser:state': CommandSignature
89+
'focus:browser:window': CommandSignature
90+
'is:automation:client:connected': CommandSignature
91+
'remote:debugger:protocol': CommandSignature
92+
'response:received': CommandSignature
93+
'key:press': CommandSignature<KeyPressParams, Promise<void>>
94+
}
95+
96+
export type OnRequestEvent = <T extends keyof AutomationCommands>(eventName: T, data: AutomationCommands[T]['dataType']) => void
6297

6398
export type OnServiceWorkerRegistrationUpdated = (data: Protocol.ServiceWorker.WorkerRegistrationUpdatedEvent) => void
6499

0 commit comments

Comments
 (0)