From 4cc7a0ed5a04cba5039ae62fa2d94f56a99d6961 Mon Sep 17 00:00:00 2001 From: Sagar E Date: Sat, 27 Jun 2026 20:36:00 +0530 Subject: [PATCH 1/2] fix(core) : dismiss keyboard after Locator.fill() --- packages/driver-mobilecli/src/driver.ts | 9 +++++++++ packages/driver-mobilenext/src/driver.ts | 9 +++++++++ packages/mobilewright-core/src/locator.ts | 6 +++++- packages/protocol/src/driver.ts | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/driver-mobilecli/src/driver.ts b/packages/driver-mobilecli/src/driver.ts index 0b44ba1..ec11bdd 100644 --- a/packages/driver-mobilecli/src/driver.ts +++ b/packages/driver-mobilecli/src/driver.ts @@ -421,6 +421,15 @@ export class MobilecliDriver implements MobilewrightDriver { await this.call('device.io.button', { button }); } + async dismissKeyboard(): Promise { + const platform = this.requireSession().platform; + if (platform === 'android') { + await this.pressButton('BACK'); + } else { + await this.pressKeys(['return']); + } + } + // ─── Screen Operations ─────────────────────────────────────── async screenshot(opts?: ScreenshotOptions): Promise { diff --git a/packages/driver-mobilenext/src/driver.ts b/packages/driver-mobilenext/src/driver.ts index 9dfc8be..03bef46 100644 --- a/packages/driver-mobilenext/src/driver.ts +++ b/packages/driver-mobilenext/src/driver.ts @@ -414,6 +414,15 @@ export class MobileNextDriver implements MobilewrightDriver { await this.call('device.io.button', { button }); } + async dismissKeyboard(): Promise { + const platform = this.requireSession().platform; + if (platform === 'android') { + await this.pressButton('BACK'); + } else { + await this.pressKeys(['return']); + } + } + // ─── Screen ───────────────────────────────────────────────── async screenshot(opts?: ScreenshotOptions): Promise { diff --git a/packages/mobilewright-core/src/locator.ts b/packages/mobilewright-core/src/locator.ts index 4bb62ca..571871e 100644 --- a/packages/mobilewright-core/src/locator.ts +++ b/packages/mobilewright-core/src/locator.ts @@ -180,10 +180,14 @@ export class Locator { return this._step('locator.clear()', () => this._tapAndClear(opts?.timeout)); } - async fill(text: string, opts?: { timeout?: number }): Promise { + async fill(text: string, opts?: { timeout?: number; dismissKeyboard?: boolean }): Promise { return this._step(`locator.fill(${JSON.stringify(text)})`, async () => { await this._tapAndClear(opts?.timeout); await this.driver.typeText(text); + const dismiss = opts?.dismissKeyboard ?? true; + if (dismiss) { + await this.driver.dismissKeyboard?.(); + } }); } diff --git a/packages/protocol/src/driver.ts b/packages/protocol/src/driver.ts index c429924..e23ba50 100644 --- a/packages/protocol/src/driver.ts +++ b/packages/protocol/src/driver.ts @@ -85,6 +85,8 @@ export interface MobilewrightDriver { gesture(gestures: GestureSequence): Promise; /** Press a hardware button (e.g. home, back, volume). */ pressButton(button: HardwareButton): Promise; + /** Dismiss the soft keyboard if it is currently open. Optional — drivers that don't support it omit it. */ + dismissKeyboard?(): Promise; // Screen /** Capture a screenshot of the device screen as a PNG buffer. */ From 36acbc26d985a6ce35e66cc3e2ac86112f2f8b8b Mon Sep 17 00:00:00 2001 From: Sagar E Date: Sat, 27 Jun 2026 21:10:32 +0530 Subject: [PATCH 2/2] fix(drivers): only dismiss keyboard when text input is focused --- packages/driver-mobilecli/src/driver.ts | 30 ++++++++++++++++++++++-- packages/driver-mobilenext/src/driver.ts | 30 ++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/packages/driver-mobilecli/src/driver.ts b/packages/driver-mobilecli/src/driver.ts index ec11bdd..ea415e6 100644 --- a/packages/driver-mobilecli/src/driver.ts +++ b/packages/driver-mobilecli/src/driver.ts @@ -423,10 +423,18 @@ export class MobilecliDriver implements MobilewrightDriver { async dismissKeyboard(): Promise { const platform = this.requireSession().platform; + + // Only dismiss if a text input is currently focused — otherwise the + // dismiss action (BACK / Done) could navigate back or submit a form. + const roots = await this.getViewHierarchy(); + if (!focusedTextInputExists(roots)) { + return; + } + if (platform === 'android') { - await this.pressButton('BACK'); + await this.pressKeys(['back']); } else { - await this.pressKeys(['return']); + await this.pressKeys(['Done']); } } @@ -630,3 +638,21 @@ export class MobilecliDriver implements MobilewrightDriver { return this.session; } } + +/** Recursively scan for a focused text-input element. Returns true when found. */ +function focusedTextInputExists(nodes: ViewNode[]): boolean { + for (const node of nodes) { + if (node.isFocused && isTextInputType(node.type)) { + return true; + } + if (node.children.length > 0 && focusedTextInputExists(node.children)) { + return true; + } + } + return false; +} + +function isTextInputType(type: string): boolean { + const lower = type.toLowerCase(); + return lower.includes('text') || lower.includes('edit') || lower.includes('search'); +} diff --git a/packages/driver-mobilenext/src/driver.ts b/packages/driver-mobilenext/src/driver.ts index 03bef46..23c2769 100644 --- a/packages/driver-mobilenext/src/driver.ts +++ b/packages/driver-mobilenext/src/driver.ts @@ -416,10 +416,18 @@ export class MobileNextDriver implements MobilewrightDriver { async dismissKeyboard(): Promise { const platform = this.requireSession().platform; + + // Only dismiss if a text input is currently focused — otherwise the + // dismiss action (BACK / Done) could navigate back or submit a form. + const roots = await this.getViewHierarchy(); + if (!focusedTextInputExists(roots)) { + return; + } + if (platform === 'android') { - await this.pressButton('BACK'); + await this.pressKeys(['back']); } else { - await this.pressKeys(['return']); + await this.pressKeys(['Done']); } } @@ -603,3 +611,21 @@ export class MobileNextDriver implements MobilewrightDriver { return this.session; } } + +/** Recursively scan for a focused text-input element. Returns true when found. */ +function focusedTextInputExists(nodes: ViewNode[]): boolean { + for (const node of nodes) { + if (node.isFocused && isTextInputType(node.type)) { + return true; + } + if (node.children.length > 0 && focusedTextInputExists(node.children)) { + return true; + } + } + return false; +} + +function isTextInputType(type: string): boolean { + const lower = type.toLowerCase(); + return lower.includes('text') || lower.includes('edit') || lower.includes('search'); +}