diff --git a/docs/api/README.md b/docs/api/README.md index a63f3fba..5643b12a 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -67,7 +67,7 @@ ___ #### Defined in -[SchemaUtils.ts:452](https://github.com/puppeteer/replay/blob/main/src/SchemaUtils.ts#L452) +[SchemaUtils.ts:451](https://github.com/puppeteer/replay/blob/main/src/SchemaUtils.ts#L451) ___ @@ -88,7 +88,7 @@ ___ #### Defined in -[SchemaUtils.ts:380](https://github.com/puppeteer/replay/blob/main/src/SchemaUtils.ts#L380) +[SchemaUtils.ts:379](https://github.com/puppeteer/replay/blob/main/src/SchemaUtils.ts#L379) ___ diff --git a/docs/api/classes/PuppeteerRunnerExtension.md b/docs/api/classes/PuppeteerRunnerExtension.md index 6cb4758a..d9848398 100644 --- a/docs/api/classes/PuppeteerRunnerExtension.md +++ b/docs/api/classes/PuppeteerRunnerExtension.md @@ -43,7 +43,7 @@ #### Defined in -[PuppeteerRunnerExtension.ts:29](https://github.com/puppeteer/replay/blob/main/src/PuppeteerRunnerExtension.ts#L29) +[PuppeteerRunnerExtension.ts:36](https://github.com/puppeteer/replay/blob/main/src/PuppeteerRunnerExtension.ts#L36) ## Methods @@ -166,4 +166,4 @@ ___ #### Defined in -[PuppeteerRunnerExtension.ts:50](https://github.com/puppeteer/replay/blob/main/src/PuppeteerRunnerExtension.ts#L50) +[PuppeteerRunnerExtension.ts:57](https://github.com/puppeteer/replay/blob/main/src/PuppeteerRunnerExtension.ts#L57) diff --git a/src/PuppeteerRunnerExtension.ts b/src/PuppeteerRunnerExtension.ts index b6b3abe0..9877571a 100644 --- a/src/PuppeteerRunnerExtension.ts +++ b/src/PuppeteerRunnerExtension.ts @@ -15,7 +15,14 @@ */ import { RunnerExtension } from './RunnerExtension.js'; -import { UserFlow, Step, WaitForElementStep, Selector, Key } from './Schema.js'; +import { + UserFlow, + Step, + WaitForElementStep, + Selector, + Key, + ChangeStep, +} from './Schema.js'; import { assertAllStepTypesAreHandled, typeableInputTypes, @@ -159,46 +166,12 @@ export class PuppeteerRunnerExtension extends RunnerExtension { (el: Element) => (el as HTMLInputElement).type ); startWaitingForEvents(); - if (typeableInputTypes.has(inputType)) { - const textToType = await element.evaluate( - (el: Element, newValue: string) => { - /* c8 ignore next 13 */ - const input = el as HTMLInputElement; - if ( - newValue.length <= input.value.length || - !newValue.startsWith(input.value) - ) { - input.value = ''; - return newValue; - } - const originalValue = input.value; - // Move cursor to the end of the common prefix. - input.value = ''; - input.value = originalValue; - return newValue.substring(originalValue.length); - }, - step.value - ); - await element.type(textToType); - // If we type into a select element, blur and re-focus the - // element to make sure that the previously opened select - // dropdown is closed. - await element.evaluateHandle((el: Element) => { - const htmlEl = el as HTMLElement; - if (htmlEl.tagName === 'SELECT') { - htmlEl.blur(); - htmlEl.focus(); - } - }); + if (inputType === 'select-one') { + await this.changeSelectElement(step, element); + } else if (typeableInputTypes.has(inputType)) { + await this.typeIntoElement(step, element); } else { - await element.focus(); - await element.evaluate((el: Element, value: string) => { - /* c8 ignore next 4 */ - const input = el as HTMLInputElement; - input.value = value; - input.dispatchEvent(new Event('input', { bubbles: true })); - input.dispatchEvent(new Event('change', { bubbles: true })); - }, step.value); + await this.changeElementValue(step, element); } await element.dispose(); } @@ -278,6 +251,59 @@ export class PuppeteerRunnerExtension extends RunnerExtension { await assertedEventsPromise; } + + /** + * @internal + */ + async typeIntoElement(step: ChangeStep, element: ElementHandle) { + const textToType = await element.evaluate( + (el: Element, newValue: string) => { + /* c8 ignore next 13 */ + const input = el as HTMLInputElement; + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + // Move cursor to the end of the common prefix. + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, + step.value + ); + await element.type(textToType); + } + + /** + * @internal + */ + async changeElementValue(step: ChangeStep, element: ElementHandle) { + await element.focus(); + await element.evaluate((el: Element, value: string) => { + /* c8 ignore next 4 */ + const input = el as HTMLInputElement; + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, step.value); + } + + /** + * @internal + */ + async changeSelectElement(step: ChangeStep, element: ElementHandle) { + await element.select(step.value); + await element.evaluateHandle((el: Element) => { + /* c8 ignore next 3 */ + const htmlEl = el as HTMLElement; + htmlEl.blur(); + htmlEl.focus(); + }); + } } export class PuppeteerRunnerOwningBrowserExtension extends PuppeteerRunnerExtension { @@ -650,6 +676,7 @@ interface ElementHandle } ): Promise | null>; asElement(): ElementHandle | null; + select(...args: string[]): Promise; } interface CDPSession { diff --git a/src/PuppeteerStringifyExtension.ts b/src/PuppeteerStringifyExtension.ts index a1659d1c..6ae48f78 100644 --- a/src/PuppeteerStringifyExtension.ts +++ b/src/PuppeteerStringifyExtension.ts @@ -145,8 +145,12 @@ export class PuppeteerStringifyExtension extends StringifyExtension { #appendChangeStep(out: LineWriter, step: ChangeStep): void { this.#appendWaitForSelector(out, step); out.appendLine('const type = await element.evaluate(el => el.type);'); + out.appendLine(`if (["select-one"].includes(type)) {`); + out.appendLine(` await element.select(${formatAsJSLiteral(step.value)});`); out.appendLine( - `if (${JSON.stringify(Array.from(typeableInputTypes))}.includes(type)) {` + `} else if (${JSON.stringify( + Array.from(typeableInputTypes) + )}.includes(type)) {` ); out.appendLine(` await element.type(${formatAsJSLiteral(step.value)});`); out.appendLine('} else {'); diff --git a/src/SchemaUtils.ts b/src/SchemaUtils.ts index c35f677e..e7609f75 100644 --- a/src/SchemaUtils.ts +++ b/src/SchemaUtils.ts @@ -47,7 +47,6 @@ export function assertAllStepTypesAreHandled(s: Step): never { export const typeableInputTypes = new Set([ 'textarea', - 'select-one', 'text', 'url', 'tel', diff --git a/test/PuppeteerStringifyExtension_test.ts b/test/PuppeteerStringifyExtension_test.ts index 5d6be58e..fc554a2b 100644 --- a/test/PuppeteerStringifyExtension_test.ts +++ b/test/PuppeteerStringifyExtension_test.ts @@ -115,7 +115,9 @@ describe('PuppeteerStringifyExtension', () => { const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); await scrollIntoViewIfNeeded(element, timeout); const type = await element.evaluate(el => el.type); - if (["textarea","select-one","text","url","tel","search","password","number","email"].includes(type)) { + if (["select-one"].includes(type)) { + await element.select("Hello World"); + } else if (["textarea","text","url","tel","search","password","number","email"].includes(type)) { await element.type("Hello World"); } else { await element.focus(); @@ -148,7 +150,9 @@ describe('PuppeteerStringifyExtension', () => { const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); await scrollIntoViewIfNeeded(element, timeout); const type = await element.evaluate(el => el.type); - if (["textarea","select-one","text","url","tel","search","password","number","email"].includes(type)) { + if (["select-one"].includes(type)) { + await element.select("#333333"); + } else if (["textarea","text","url","tel","search","password","number","email"].includes(type)) { await element.type("#333333"); } else { await element.focus(); diff --git a/test/resources/select.html b/test/resources/select.html index fd931aca..a6fb0233 100644 --- a/test/resources/select.html +++ b/test/resources/select.html @@ -17,7 +17,7 @@