import { TestRunnerPlugin } from '@web/test-runner-core'; import type { ChromeLauncher, puppeteerCore } from '@web/test-runner-chrome'; import type { PlaywrightLauncher } from '@web/test-runner-playwright'; import type { WebdriverLauncher } from '@web/test-runner-webdriver'; type TypePayload = { type: string }; type PressPayload = { press: string }; type DownPayload = { down: string }; type UpPayload = { up: string }; export type SendKeysPayload = TypePayload | PressPayload | DownPayload | UpPayload; function isObject(payload: unknown): payload is Record { return payload != null && typeof payload === 'object'; } function isSendKeysPayload(payload: unknown): boolean { const validOptions = ['type', 'press', 'down', 'up']; if (!isObject(payload)) throw new Error('You must provide a `SendKeysPayload` object'); const numberOfValidOptions = Object.keys(payload).filter(key => validOptions.includes(key), ).length; const unknownOptions = Object.keys(payload).filter(key => !validOptions.includes(key)); if (numberOfValidOptions > 1) throw new Error( `You must provide ONLY one of the following properties to pass to the browser runner: ${validOptions.join( ', ', )}.`, ); if (numberOfValidOptions === 0) throw new Error( `You must provide one of the following properties to pass to the browser runner: ${validOptions.join( ', ', )}.`, ); if (unknownOptions.length > 0) { throw new Error('Unknown options `' + unknownOptions.join(', ') + '` present.'); } return true; } function isTypePayload(payload: SendKeysPayload): payload is TypePayload { return 'type' in payload; } function isPressPayload(payload: SendKeysPayload): payload is PressPayload { return 'press' in payload; } function isDownPayload(payload: SendKeysPayload): payload is DownPayload { return 'down' in payload; } function isUpPayload(payload: SendKeysPayload): payload is UpPayload { return 'up' in payload; } export function sendKeysPlugin(): TestRunnerPlugin { return { name: 'send-keys-command', async executeCommand({ command, payload, session }): Promise { if (command === 'send-keys') { if (!isSendKeysPayload(payload) || !payload) { throw new Error('You must provide a `SendKeysPayload` object'); } // handle specific behavior for playwright if (session.browser.type === 'playwright') { const page = (session.browser as PlaywrightLauncher).getPage(session.id); if (isTypePayload(payload)) { await page.keyboard.type(payload.type); return true; } else if (isPressPayload(payload)) { await page.keyboard.press(payload.press); return true; } else if (isDownPayload(payload)) { await page.keyboard.down(payload.down); return true; } else if (isUpPayload(payload)) { await page.keyboard.up(payload.up); return true; } } // handle specific behavior for puppeteer if (session.browser.type === 'puppeteer') { const page = (session.browser as ChromeLauncher).getPage(session.id); if (isTypePayload(payload)) { await page.keyboard.type(payload.type); return true; } else if (isPressPayload(payload)) { await page.keyboard.press(payload.press as puppeteerCore.KeyInput); return true; } else if (isDownPayload(payload)) { await page.keyboard.down(payload.down as puppeteerCore.KeyInput); return true; } else if (isUpPayload(payload)) { await page.keyboard.up(payload.up as puppeteerCore.KeyInput); return true; } } // handle specific behavior for webdriver if (session.browser.type === 'webdriver') { const browser = session.browser as WebdriverLauncher; if (isTypePayload(payload)) { await browser.sendKeys(session.id, payload.type.split('')); return true; } else if (isPressPayload(payload)) { await browser.sendKeys(session.id, [payload.press]); return true; } else { throw new Error('Only "press" and "type" are supported by webdriver.'); } } // you might not be able to support all browser launchers throw new Error(`Sending keys is not supported for browser type ${session.browser.type}.`); } }, }; }