Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions src/platforms/android/__tests__/input-actions-fill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,14 @@ test('verifyAndroidFilledTextInHierarchy redacts masked password values on wrong
);
});

test('readAndroidTextAtPointInHierarchy prefers focused edit text over point fallback', () => {
assert.equal(readAndroidTextAtPointInHierarchy(focusedEditHierarchy(), 10, 10), 'focused value');
test('readAndroidTextAtPointInHierarchy reads the EditText under the requested point', () => {
const hierarchy = `<?xml version="1.0" encoding="UTF-8"?><hierarchy>
<node package="com.example" class="android.widget.EditText" text="Ada Lovelace" resource-id="com.example:id/field-name" focused="false" bounds="[0,0][200,100]"/>
<node package="com.example" class="android.widget.EditText" text="fallback@example.com" resource-id="com.example:id/field-email" focused="true" bounds="[0,200][200,300]"/>
</hierarchy>`;

assert.equal(readAndroidTextAtPointInHierarchy(hierarchy, 100, 50), 'Ada Lovelace');
assert.equal(readAndroidTextAtPointInHierarchy(hierarchy, 100, 250), 'fallback@example.com');
});

const IME_RESOURCE_ID = 'com.google.android.inputmethod.latin:id/0_resource_name_obfuscated';
Expand Down Expand Up @@ -467,13 +473,6 @@ function androidInputXml(options: { text: string }): string {
return `<?xml version="1.0" encoding="UTF-8"?><hierarchy><node package="com.example" class="android.widget.EditText" text="${options.text}" focused="true" bounds="[0,0][200,100]"/></hierarchy>`;
}

function focusedEditHierarchy(): string {
return `<?xml version="1.0" encoding="UTF-8"?><hierarchy>
<node package="com.example" class="android.widget.TextView" text="point fallback" focused="false" bounds="[0,0][200,100]"/>
<node package="com.example" class="android.widget.EditText" text="focused value" focused="true" bounds="[300,300][500,400]"/>
</hierarchy>`;
}

function appPackageWithInputMethodSubstringHierarchy(): string {
return `<?xml version="1.0" encoding="UTF-8"?><hierarchy>
<node package="com.example.shop" class="android.widget.EditText" text="Search Products" resource-id="com.example.shop:id/search" focused="false" bounds="[0,0][300,100]"/>
Expand Down
4 changes: 3 additions & 1 deletion src/platforms/android/fill-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ export function readAndroidTextAtPointInHierarchy(
x: number,
y: number,
): string | null {
return inspectAndroidTextAtPointInHierarchy(xml, x, y).actualInput?.text ?? null;
// Reads are point-targeted: a focused sibling may be a different app field or an IME
// composing surface, so it must not override the node that contains the requested point.
return inspectAndroidTextAtPointInHierarchy(xml, x, y).targetInput?.text ?? null;
}

export function androidFillFailureMessage(verification: AndroidFillVerification | null): string {
Expand Down
13 changes: 9 additions & 4 deletions test/integration/android-emulator-e2e/live-form-scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type { CliJsonResult } from '../cli-json.ts';
import {
assertElementText,
assertFilesDiffer,
assertJsonContains,
assertWaitText,
capturePng,
requireAndroidResourceId,
Expand Down Expand Up @@ -36,16 +35,22 @@ export async function assertFormInput(context: LiveContext): Promise<void> {
'id="field-name"',
'Ada Łovelace',
]);
await runStep(context, 'fill email field', ['fill', 'id="field-email"', 'ada@example']);
const name = await runStep(context, 'read filled full name', ['get', 'text', 'id="field-name"']);
assertJsonContains(name, 'Ada Łovelace', 'Unicode name should be observable in Android UI');
assert.equal(name.json?.data?.text, 'Ada Łovelace', JSON.stringify(name.json));
const emailText = await runStep(context, 'read filled email', [
'get',
'text',
'id="field-email"',
]);
assert.equal(emailText.json?.data?.text, 'ada@example', JSON.stringify(emailText.json));
verifyCommand(context, C.fill, 'Unicode replacement text is read back from Android UI');
verifyBehavior(
context,
'test-ime-unicode-input',
`active ${ANDROID_TEST_IME_PACKAGE} committed and read back Ada Łovelace`,
`active ${ANDROID_TEST_IME_PACKAGE} committed and both filled values were read from their own Android fields`,
);

await runStep(context, 'fill email field', ['fill', 'id="field-email"', 'ada@example']);
const snapshot = await runStep(context, 'locate email Android resource-id', ['snapshot', '-i']);
const email = requireAndroidResourceId(snapshot, 'field-email');
await runStep(context, 'focus email by snapshot-derived point', [
Expand Down
Loading