blob: 3d3b1d20f6fde01b02372d54914f27c71b2d117d [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser.accessibility;
import static android.content.Context.CLIPBOARD_SERVICE;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_HTML_ELEMENT_STRING;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_PROGRESS_VALUE;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_END_INT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_START_INT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_ACCESSIBILITY_FOCUS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLEAR_FOCUS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_COPY;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CUT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_FOCUS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_NEXT_HTML_ELEMENT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_PAGE_UP;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_PASTE;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_PREVIOUS_HTML_ELEMENT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_BACKWARD;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_FORWARD;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_PROGRESS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_SELECTION;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_TEXT;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SHOW_ON_SCREEN;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_CHARACTER;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_PARAGRAPH;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_WORD;
import static org.chromium.content.browser.accessibility.AccessibilityContentShellTestUtils.NODE_TIMEOUT_ERROR;
import static org.chromium.content.browser.accessibility.AccessibilityContentShellTestUtils.sClassNameMatcher;
import static org.chromium.content.browser.accessibility.AccessibilityContentShellTestUtils.sInputTypeMatcher;
import static org.chromium.content.browser.accessibility.AccessibilityContentShellTestUtils.sRangeInfoMatcher;
import static org.chromium.content.browser.accessibility.AccessibilityContentShellTestUtils.sTextMatcher;
import static org.chromium.content.browser.accessibility.AccessibilityContentShellTestUtils.sViewIdResourceNameMatcher;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.CACHE_MAX_NODES_HISTOGRAM;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.CACHE_PERCENTAGE_RETRIEVED_FROM_CACHE_HISTOGRAM;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.EVENTS_DROPPED_HISTOGRAM;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.ONE_HUNDRED_PERCENT_HISTOGRAM;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_BASIC;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_COMPLETE;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_FORM_CONTROLS;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.PERCENTAGE_DROPPED_HISTOGRAM;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_BASIC;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_COMPLETE;
import static org.chromium.content.browser.accessibility.AccessibilityHistogramRecorder.PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_FORM_CONTROLS;
import static org.chromium.content.browser.accessibility.AccessibilityNodeInfoBuilder.EXTRAS_DATA_REQUEST_IMAGE_DATA_KEY;
import static org.chromium.content.browser.accessibility.AccessibilityNodeInfoBuilder.EXTRAS_KEY_CHROME_ROLE;
import static org.chromium.content.browser.accessibility.AccessibilityNodeInfoBuilder.EXTRAS_KEY_IMAGE_DATA;
import static org.chromium.content.browser.accessibility.AccessibilityNodeInfoBuilder.EXTRAS_KEY_OFFSCREEN;
import static org.chromium.content.browser.accessibility.AccessibilityNodeInfoBuilder.EXTRAS_KEY_UNCLIPPED_BOTTOM;
import static org.chromium.content.browser.accessibility.AccessibilityNodeInfoBuilder.EXTRAS_KEY_UNCLIPPED_TOP;
import static org.chromium.ui.accessibility.AccessibilityState.EVENT_TYPE_MASK_NONE;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.text.InputType;
import android.text.Spannable;
import android.text.style.SuggestionSpan;
import android.view.accessibility.AccessibilityEvent;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.FeatureList;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.ContentFeatureList;
import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.accessibility.AccessibilityState;
import org.chromium.ui.test.util.UiRestriction;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
/**
* Tests for WebContentsAccessibility. Actually tests WebContentsAccessibilityImpl that
* implements the interface.
*/
@RunWith(ContentJUnit4ClassRunner.class)
@DoNotBatch(reason = "Flaky tests")
@SuppressLint("VisibleForTests")
public class WebContentsAccessibilityTest {
// Test output error messages
private static final String DISABLED_COMBOBOX_ERROR =
"disabled combobox child elements should not be clickable";
private static final String THRESHOLD_ERROR =
"Too many TYPE_WINDOW_CONTENT_CHANGED events received in an atomic update.";
private static final String THRESHOLD_LOW_EVENT_COUNT_ERROR =
"Expected more TYPE_WINDOW_CONTENT_CHANGED events"
+ "in an atomic update, is throttling still necessary?";
private static final String SPELLING_ERROR =
"node should have a Spannable with spelling correction for given text.";
private static final String INPUT_RANGE_VALUE_MISMATCH =
"Value for <input type='range'> is incorrect, did you honor 'step' value?";
private static final String INPUT_RANGE_EVENT_ERROR =
"TYPE_VIEW_SCROLLED event not received before timeout.";
private static final String CACHING_ERROR = "AccessibilityNodeInfo cache has stale data";
private static final String NODE_EXTRAS_UNCLIPPED_ERROR =
"AccessibilityNodeInfo object should have unclipped bounds in extras bundle";
private static final String EVENT_TYPE_MASK_ERROR =
"Conversion of event masks to event types not correct.";
private static final String TEXT_SELECTION_AND_TRAVERSAL_ERROR =
"Expected to receive both a traversal and selection text event";
private static final String BOUNDING_BOX_ERROR =
"Expected bounding box to have updated values.";
private static final String UMA_HISTOGRAM_ERROR =
"Expected UMA histograms did not match recorded value.";
private static final String VISIBLE_TO_USER_ERROR =
"AccessibilityNodeInfo object has incorrect visibleToUser value";
private static final String OFFSCREEN_BUNDLE_EXTRA_ERROR =
"AccessibilityNodeInfo object has incorrect Bundle extras for offscreen boolean.";
private static final String PERFORM_ACTION_ERROR =
"performAction did not update node as expected.";
private static final String IMAGE_DATA_BUNDLE_EXTRA_ERROR =
"AccessibilityNodeInfo object does not have Bundle extra containing image data.";
private static final String FOCUSING_ERROR =
"Expected focus to be on a different node than it is.";
// ContentFeatureList maps used for various tests.
private static final Map<String, Boolean> ON_DEMAND_ON_AXMODES_ON =
Map.of(ContentFeatureList.ON_DEMAND_ACCESSIBILITY_EVENTS, true,
ContentFeatureList.ACCESSIBILITY_AX_MODES, true);
// Constant values for unit tests
private static final int UNSUPPRESSED_EXPECTED_COUNT = 15;
private AccessibilityNodeInfoCompat mNodeInfo;
private AccessibilityContentShellTestData mTestData;
@Rule
public AccessibilityContentShellActivityTestRule mActivityTestRule =
new AccessibilityContentShellActivityTestRule();
/**
* Helper methods for setup of a basic web contents accessibility unit test.
*
* These methods replace the usual setUp() method annotated with @Before because we wish to
* load different data with each test, but the process is the same for all tests.
*
* Leaving a commented @Before annotation on each method as a reminder/context clue.
*/
/* @Before */
protected void setupTestWithHTML(String html) {
mActivityTestRule.launchContentShellWithUrl(UrlUtils.encodeHtmlDataUri(html));
mActivityTestRule.waitForActiveShellToBeDoneLoading();
mActivityTestRule.setupTestFramework();
mActivityTestRule.setAccessibilityDelegate();
mTestData = AccessibilityContentShellTestData.getInstance();
mActivityTestRule.sendReadyForTestSignal();
}
/* @Before */
protected void setupTestFromFile(String filepath) {
mActivityTestRule.launchContentShellWithUrl(UrlUtils.getIsolatedTestFileUrl(filepath));
mActivityTestRule.waitForActiveShellToBeDoneLoading();
mActivityTestRule.setupTestFramework();
mActivityTestRule.setAccessibilityDelegate();
mTestData = AccessibilityContentShellTestData.getInstance();
mActivityTestRule.sendReadyForTestSignal();
}
/**
* Helper method to tear down our tests so we can start the next test clean.
*/
@After
public void tearDown() {
mTestData = null;
mNodeInfo = null;
}
// Helper pass-through methods to make tests easier to read.
private <T> int waitForNodeMatching(
AccessibilityContentShellTestUtils.AccessibilityNodeInfoMatcher<T> matcher, T element) {
return mActivityTestRule.waitForNodeMatching(matcher, element);
}
private boolean performActionOnUiThread(
int viewId, AccessibilityNodeInfoCompat.AccessibilityActionCompat action, Bundle args)
throws ExecutionException {
return mActivityTestRule.performActionOnUiThread(viewId, action.getId(), args);
}
private boolean performActionOnUiThread(int viewId,
AccessibilityNodeInfoCompat.AccessibilityActionCompat action, Bundle args,
Callable<Boolean> criteria) throws ExecutionException, Throwable {
return mActivityTestRule.performActionOnUiThread(viewId, action.getId(), args, criteria);
}
private void executeJS(String method) {
mActivityTestRule.executeJS(method);
}
private void focusNode(int virtualViewId) throws Throwable {
mActivityTestRule.focusNode(virtualViewId);
}
public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
return TestThreadUtils.runOnUiThreadBlockingNoException(
() -> mActivityTestRule.mNodeProvider.createAccessibilityNodeInfo(virtualViewId));
}
/**
* Helper method for sending text related events and confirming that the associated text
* selection and traversal events have been dispatched before continuing with test.
*
* @param viewId int virtualViewId of the text field
* @param action AccessibilityActionCompat action to perform
* @param args Bundle optional arguments
* @throws ExecutionException Error
*/
private void performTextActionOnUiThread(
int viewId, AccessibilityNodeInfoCompat.AccessibilityActionCompat action, Bundle args)
throws ExecutionException {
// Reset values for traversal and selection events.
mTestData.setReceivedTraversalEvent(false);
mTestData.setReceivedSelectionEvent(false);
// Perform our text selection/traversal action.
mActivityTestRule.performActionOnUiThread(viewId, action.getId(), args);
// Poll until both events have been confirmed as received
CriteriaHelper.pollUiThread(() -> {
return mTestData.hasReceivedTraversalEvent() && mTestData.hasReceivedSelectionEvent();
}, TEXT_SELECTION_AND_TRAVERSAL_ERROR);
}
// ------------------ Tests of WebContentsAccessibilityImpl methods ------------------ //
/**
* Ensure we throttle TYPE_WINDOW_CONTENT_CHANGED events for large tree updates.
*/
@Test
@SmallTest
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
public void testMaxContentChangedEventsFired_default() throws Throwable {
// Build a simple web page with complex visibility change.
setupTestFromFile("content/test/data/android/type_window_content_changed_events.html");
// Determine the current max events to fire
int maxEvents = mActivityTestRule.mWcax.getMaxContentChangedEventsToFireForTesting();
// Find the button node.
int vvid = waitForNodeMatching(sClassNameMatcher, "android.widget.Button");
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertEquals(NODE_TIMEOUT_ERROR, "Expand All", mNodeInfo.getText());
// Run JS code to expand comboboxes.
executeJS("expandComboboxes()");
// Poll until the JS method is confirmed to have finished.
CriteriaHelper.pollUiThread(() -> {
return createAccessibilityNodeInfo(vvid).getText().toString().equals("Done");
}, NODE_TIMEOUT_ERROR);
// Signal end of test
mActivityTestRule.sendEndOfTestSignal();
// Verify number of events processed, allow for multiple atomic updates.
int eventCount = mTestData.getTypeWindowContentChangedCount();
Assert.assertTrue(thresholdError(eventCount, maxEvents), eventCount <= (maxEvents * 3));
}
/**
* Ensure we need to throttle TYPE_WINDOW_CONTENT_CHANGED events for some large tree updates.
*/
@Test
@SmallTest
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
public void testMaxContentChangedEventsFired_largeLimit() throws Throwable {
// Build a simple web page with complex visibility change.
setupTestFromFile("content/test/data/android/type_window_content_changed_events.html");
// "Disable" event suppression by setting an arbitrarily high max events value.
mActivityTestRule.mWcax.setMaxContentChangedEventsToFireForTesting(Integer.MAX_VALUE);
// Find the button node.
int vvid = waitForNodeMatching(sClassNameMatcher, "android.widget.Button");
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertEquals(NODE_TIMEOUT_ERROR, "Expand All", mNodeInfo.getText());
// Run JS code to expand comboboxes.
executeJS("expandComboboxes()");
// Poll until the JS method is confirmed to have finished.
CriteriaHelper.pollUiThread(() -> {
return createAccessibilityNodeInfo(vvid).getText().toString().equals("Done");
}, NODE_TIMEOUT_ERROR);
// Signal end of test
mActivityTestRule.sendEndOfTestSignal();
// Verify number of events processed
int eventCount = mTestData.getTypeWindowContentChangedCount();
Assert.assertTrue(lowThresholdError(eventCount), eventCount > UNSUPPRESSED_EXPECTED_COUNT);
}
/**
* Test logic for converting event type masks to a list of relevant event types.
*/
@Test
@SmallTest
public void testMaskToEventTypeConversion() {
// Build a simple web page.
setupTestWithHTML("<p>Test page</p>");
// Create some event masks with known outcomes.
int serviceEventMask_empty = 0;
int serviceEventMask_full = Integer.MAX_VALUE;
int serviceEventMask_test = AccessibilityEvent.TYPE_VIEW_CLICKED
| AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED
| AccessibilityEvent.TYPE_VIEW_SCROLLED | AccessibilityEvent.TYPE_VIEW_SELECTED
| AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
// Convert each mask to a set of eventTypes.
Set<Integer> outcome_empty =
mActivityTestRule.mWcax.convertMaskToEventTypes(serviceEventMask_empty);
Set<Integer> outcome_full =
mActivityTestRule.mWcax.convertMaskToEventTypes(serviceEventMask_full);
Set<Integer> outcome_test =
mActivityTestRule.mWcax.convertMaskToEventTypes(serviceEventMask_test);
// Verify results.
Assert.assertNotNull(EVENT_TYPE_MASK_ERROR, outcome_empty);
Assert.assertTrue(EVENT_TYPE_MASK_ERROR, outcome_empty.isEmpty());
Assert.assertNotNull(EVENT_TYPE_MASK_ERROR, outcome_full);
Assert.assertEquals(EVENT_TYPE_MASK_ERROR, 31, outcome_full.size());
Set<Integer> expected_test = new HashSet<Integer>(Arrays.asList(
AccessibilityEvent.TYPE_VIEW_CLICKED, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED,
AccessibilityEvent.TYPE_VIEW_FOCUSED, AccessibilityEvent.TYPE_VIEW_SCROLLED,
AccessibilityEvent.TYPE_VIEW_SELECTED,
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END));
Assert.assertNotNull(EVENT_TYPE_MASK_ERROR, outcome_test);
Assert.assertEquals(EVENT_TYPE_MASK_ERROR, expected_test, outcome_test);
}
/**
* Test that UMA histograms are recorded for the OnDemand AT feature and AX Mode Complete.
*/
@Test
@SmallTest
public void testUMAHistograms_OnDemand_AXModeComplete() throws Throwable {
// Build a simple web page with a few nodes to traverse.
setupTestWithHTML("<p>This is a test 1</p>\n"
+ "<p>This is a test 2</p>\n"
+ "<p>This is a test 3</p>");
// Set the relevant features and accessibility state.
FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
TestThreadUtils.runOnUiThreadBlocking(() -> {
AccessibilityState.setIsScreenReaderEnabledForTesting(true);
AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
});
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM, 0)
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_COMPLETE, 0)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_BASIC)
.expectIntRecord(EVENTS_DROPPED_HISTOGRAM, 0)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_COMPLETE)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_BASIC)
.build();
performHistogramActions();
histogramWatcher.assertExpected();
}
/**
* Test that UMA histograms are recorded for the OnDemand AT feature and AX Mode Form Controls.
*/
@Test
@SmallTest
public void testUMAHistograms_OnDemand_AXModeFormControls() throws Throwable {
// Build a simple web page with a few nodes to traverse.
setupTestWithHTML("<p>This is a test 1</p>\n"
+ "<p>This is a test 2</p>\n"
+ "<p>This is a test 3</p>");
// Set the relevant features and accessibility state.
FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
TestThreadUtils.runOnUiThreadBlocking(() -> {
AccessibilityState.setIsScreenReaderEnabledForTesting(false);
AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(true);
});
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM, 0)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_COMPLETE)
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_FORM_CONTROLS, 0)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_BASIC)
.expectIntRecord(EVENTS_DROPPED_HISTOGRAM, 0)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_COMPLETE)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_BASIC)
.build();
performHistogramActions();
histogramWatcher.assertExpected();
}
/**
* Test that UMA histograms are recorded for the OnDemand AT feature and AX Mode Basic.
*/
@Test
@SmallTest
public void testUMAHistograms_OnDemand_AXModeBasic() throws Throwable {
// Build a simple web page with a few nodes to traverse.
setupTestWithHTML("<p>This is a test 1</p>\n"
+ "<p>This is a test 2</p>\n"
+ "<p>This is a test 3</p>");
// Set the relevant features and screen reader state.
FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
TestThreadUtils.runOnUiThreadBlocking(() -> {
AccessibilityState.setIsScreenReaderEnabledForTesting(false);
AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
});
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM, 0)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_COMPLETE)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_BASIC, 0)
.expectIntRecord(EVENTS_DROPPED_HISTOGRAM, 0)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_COMPLETE)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_BASIC)
.build();
performHistogramActions();
histogramWatcher.assertExpected();
}
/**
* Test that UMA histograms are recorded for the OnDemand AT feature and AX Mode Complete
* when 100% of events are dropped.
*/
@Test
@SmallTest
public void testUMAHistograms_OnDemand_AXModeComplete_100Percent() throws Throwable {
// Build a simple web page with a few nodes to traverse.
setupTestWithHTML("<p>This is a test 1</p>\n"
+ "<p>This is a test 2</p>\n"
+ "<p>This is a test 3</p>");
// Set the relevant features and screen reader state, set event type masks to empty.
FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
TestThreadUtils.runOnUiThreadBlocking(() -> {
AccessibilityState.setEventTypeMaskForTesting(EVENT_TYPE_MASK_NONE);
AccessibilityState.setIsScreenReaderEnabledForTesting(true);
AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
});
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM, 100)
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_COMPLETE, 100)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_BASIC)
.expectIntRecord(EVENTS_DROPPED_HISTOGRAM, 3)
.expectIntRecord(ONE_HUNDRED_PERCENT_HISTOGRAM, 3)
.expectIntRecord(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_COMPLETE, 3)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_BASIC)
.build();
performHistogramActions();
histogramWatcher.assertExpected();
}
/**
* Test that UMA histograms are recorded for the OnDemand AT feature and AX Mode Form Controls
* when 100% of events are dropped.
*/
@Test
@SmallTest
public void testUMAHistograms_OnDemand_AXModeFormControls_100Percent() throws Throwable {
// Build a simple web page with a few nodes to traverse.
setupTestWithHTML("<p>This is a test 1</p>\n"
+ "<p>This is a test 2</p>\n"
+ "<p>This is a test 3</p>");
// Set the relevant features and screen reader state, set event type masks to empty.
FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
TestThreadUtils.runOnUiThreadBlocking(() -> {
AccessibilityState.setEventTypeMaskForTesting(EVENT_TYPE_MASK_NONE);
AccessibilityState.setIsScreenReaderEnabledForTesting(false);
AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(true);
});
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM, 100)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_COMPLETE)
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_FORM_CONTROLS, 100)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_BASIC)
.expectIntRecord(EVENTS_DROPPED_HISTOGRAM, 3)
.expectIntRecord(ONE_HUNDRED_PERCENT_HISTOGRAM, 3)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_COMPLETE)
.expectIntRecord(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_FORM_CONTROLS, 3)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_BASIC)
.build();
performHistogramActions();
histogramWatcher.assertExpected();
}
/**
* Test that UMA histograms are recorded for the OnDemand AT feature and AX Mode Basic
* when 100% of events are dropped.
*/
@Test
@SmallTest
public void testUMAHistograms_OnDemand_AXModeBasic_100Percent() throws Throwable {
// Build a simple web page with a few nodes to traverse.
setupTestWithHTML("<p>This is a test 1</p>\n"
+ "<p>This is a test 2</p>\n"
+ "<p>This is a test 3</p>");
// Set the relevant features and screen reader state, set event type masks to empty.
FeatureList.setTestFeatures(ON_DEMAND_ON_AXMODES_ON);
TestThreadUtils.runOnUiThreadBlocking(() -> {
AccessibilityState.setEventTypeMaskForTesting(EVENT_TYPE_MASK_NONE);
AccessibilityState.setIsScreenReaderEnabledForTesting(false);
AccessibilityState.setIsOnlyPasswordManagersEnabledForTesting(false);
});
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM, 100)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_COMPLETE)
.expectNoRecords(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectIntRecord(PERCENTAGE_DROPPED_HISTOGRAM_AXMODE_BASIC, 100)
.expectIntRecord(EVENTS_DROPPED_HISTOGRAM, 3)
.expectIntRecord(ONE_HUNDRED_PERCENT_HISTOGRAM, 3)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_COMPLETE)
.expectNoRecords(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_FORM_CONTROLS)
.expectIntRecord(ONE_HUNDRED_PERCENT_HISTOGRAM_AXMODE_BASIC, 3)
.build();
performHistogramActions();
histogramWatcher.assertExpected();
}
/**
* Test that UMA histograms are recorded for the cache statistics, including the max number of
* nodes stored in the cache, and percentage of requests retrieved from the cache.
*/
@Test
@SmallTest
public void testUMAHistograms_Cache() throws Throwable {
// Build a simple web page with a few nodes to traverse.
setupTestWithHTML("<p>This is a test 1</p>\n"
+ "<p>This is a test 2</p>\n"
+ "<p>This is a test 3</p>");
var histogramWatcher =
HistogramWatcher.newBuilder()
.expectIntRecord(CACHE_MAX_NODES_HISTOGRAM, 3)
.expectAnyRecord(CACHE_PERCENTAGE_RETRIEVED_FROM_CACHE_HISTOGRAM)
.build();
performHistogramActions();
histogramWatcher.assertExpected();
}
/**
* Test that the {resetFocus} method performs as expected with accessibility enabled.
*/
@Test
@SmallTest
public void testResetFocus() throws Throwable {
// Setup test page with example paragraphs.
setupTestWithHTML("<p id='id1'>Example Paragraph 1</p><p>Example Paragraph 2</p>");
// Find the root node, and a paragraph node, then focus the paragraph.
int rootVvid = waitForNodeMatching(sClassNameMatcher, "android.webkit.WebView");
int vvid = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
AccessibilityNodeInfoCompat rootNodeInfo = createAccessibilityNodeInfo(rootVvid);
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, rootNodeInfo);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
focusNode(vvid);
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify the root is not focused, and the paragraph is focused.
Assert.assertFalse(FOCUSING_ERROR, rootNodeInfo.isAccessibilityFocused());
Assert.assertTrue(FOCUSING_ERROR, mNodeInfo.isAccessibilityFocused());
// Use the public {resetFocus} method and verify focus has been removed.
TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.mWcax.resetFocus());
CriteriaHelper.pollUiThread(
() -> !createAccessibilityNodeInfo(vvid).isAccessibilityFocused());
rootNodeInfo = createAccessibilityNodeInfo(rootVvid);
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertFalse(FOCUSING_ERROR, rootNodeInfo.isAccessibilityFocused());
Assert.assertFalse(FOCUSING_ERROR, mNodeInfo.isAccessibilityFocused());
}
/**
* Test restoring focus of the latest focused element with the {restoreFocus} method.
*/
@Test
@SmallTest
public void testRestoreFocus() throws Throwable {
// Setup test page with example paragraphs.
setupTestWithHTML("<input id='id1'><input id='id2'>");
// Find the root node, and a paragraph node, then focus the paragraph.
int rootVvid = waitForNodeMatching(sClassNameMatcher, "android.webkit.WebView");
int vvid1 = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
int vvid2 = waitForNodeMatching(sViewIdResourceNameMatcher, "id2");
Assert.assertNotNull(NODE_TIMEOUT_ERROR, createAccessibilityNodeInfo(rootVvid));
Assert.assertNotNull(NODE_TIMEOUT_ERROR, createAccessibilityNodeInfo(vvid1));
Assert.assertNotNull(NODE_TIMEOUT_ERROR, createAccessibilityNodeInfo(vvid2));
Assert.assertFalse(
FOCUSING_ERROR, createAccessibilityNodeInfo(vvid1).isAccessibilityFocused());
focusNode(vvid1);
// Reset focus explicitly.
TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.mWcax.resetFocus());
CriteriaHelper.pollUiThread(
() -> !createAccessibilityNodeInfo(vvid1).isAccessibilityFocused());
// Restore focus, verify that it gets back.
TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.mWcax.restoreFocus());
CriteriaHelper.pollUiThread(
() -> createAccessibilityNodeInfo(vvid1).isAccessibilityFocused());
focusNode(vvid1);
focusNode(vvid2);
// Reset focus by performing an action, it covers one more way of losing focus.
Assert.assertTrue(performActionOnUiThread(vvid2, ACTION_CLEAR_ACCESSIBILITY_FOCUS, null,
() -> !createAccessibilityNodeInfo(vvid2).isAccessibilityFocused()));
// Restore focus, verify that the second (latest focused) element gets focus.
TestThreadUtils.runOnUiThreadBlocking(() -> mActivityTestRule.mWcax.restoreFocus());
CriteriaHelper.pollUiThread(
() -> createAccessibilityNodeInfo(vvid2).isAccessibilityFocused());
}
// ------------------ Tests of AccessibilityNodeInfo caching mechanism ------------------ //
/**
* Test our internal cache of |AccessibilityNodeInfo| objects for proper focus/action updates.
*/
@Test
@SmallTest
public void testNodeInfoCache_AccessibilityFocusAndActions() throws Throwable {
// Build a simple web page with two paragraphs that can be focused.
setupTestWithHTML("<div>\n"
+ " <p>Example Paragraph 1</p>\n"
+ " <p>Example Paragraph 2</p>\n"
+ "</div>");
// Define our root node and paragraph node IDs by looking for their text.
int vvIdP1 = waitForNodeMatching(sTextMatcher, "Example Paragraph 1");
int vvIdP2 = waitForNodeMatching(sTextMatcher, "Example Paragraph 2");
// Get the |AccessibilityNodeInfoCompat| objects for our nodes.
AccessibilityNodeInfoCompat nodeInfoP1 = createAccessibilityNodeInfo(vvIdP1);
AccessibilityNodeInfoCompat nodeInfoP2 = createAccessibilityNodeInfo(vvIdP2);
// Assert we have the correct nodes.
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoP1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoP2);
// Assert neither node has been focused, and both have a accessibility focusable action.
Assert.assertFalse(nodeInfoP1.isAccessibilityFocused());
Assert.assertFalse(nodeInfoP2.isAccessibilityFocused());
Assert.assertTrue(nodeInfoP1.getActionList().contains(ACTION_ACCESSIBILITY_FOCUS));
Assert.assertFalse(nodeInfoP1.getActionList().contains(ACTION_CLEAR_ACCESSIBILITY_FOCUS));
Assert.assertTrue(nodeInfoP2.getActionList().contains(ACTION_ACCESSIBILITY_FOCUS));
Assert.assertFalse(nodeInfoP2.getActionList().contains(ACTION_CLEAR_ACCESSIBILITY_FOCUS));
// Now focus each paragraph in turn and check available actions.
focusNode(vvIdP1);
nodeInfoP1 = createAccessibilityNodeInfo(vvIdP1);
nodeInfoP2 = createAccessibilityNodeInfo(vvIdP2);
Assert.assertTrue(nodeInfoP1.isAccessibilityFocused());
Assert.assertFalse(nodeInfoP1.getActionList().contains(ACTION_ACCESSIBILITY_FOCUS));
Assert.assertTrue(nodeInfoP1.getActionList().contains(ACTION_CLEAR_ACCESSIBILITY_FOCUS));
Assert.assertFalse(nodeInfoP2.isAccessibilityFocused());
Assert.assertTrue(nodeInfoP2.getActionList().contains(ACTION_ACCESSIBILITY_FOCUS));
Assert.assertFalse(nodeInfoP2.getActionList().contains(ACTION_CLEAR_ACCESSIBILITY_FOCUS));
// Focus second paragraph to confirm proper cache updates.
focusNode(vvIdP2);
nodeInfoP1 = createAccessibilityNodeInfo(vvIdP1);
nodeInfoP2 = createAccessibilityNodeInfo(vvIdP2);
Assert.assertFalse(nodeInfoP1.isAccessibilityFocused());
Assert.assertTrue(nodeInfoP1.getActionList().contains(ACTION_ACCESSIBILITY_FOCUS));
Assert.assertFalse(nodeInfoP1.getActionList().contains(ACTION_CLEAR_ACCESSIBILITY_FOCUS));
Assert.assertTrue(nodeInfoP2.isAccessibilityFocused());
Assert.assertFalse(nodeInfoP2.getActionList().contains(ACTION_ACCESSIBILITY_FOCUS));
Assert.assertTrue(nodeInfoP2.getActionList().contains(ACTION_CLEAR_ACCESSIBILITY_FOCUS));
}
/**
* Test our internal cache of |AccessibilityNodeInfo| objects for proper leaf node updates.
*/
@Test
@SmallTest
public void testNodeInfoCache_LeafNodeText() throws Throwable {
// Build a simple web page with a text node inside a leaf node.
setupTestFromFile("content/test/data/android/leaf_node_updates.html");
// Find the encompassing <div> node.
int vvIdDiv = waitForNodeMatching(sViewIdResourceNameMatcher, "test");
mNodeInfo = createAccessibilityNodeInfo(vvIdDiv);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertEquals(NODE_TIMEOUT_ERROR, "Example text 1", mNodeInfo.getText());
// Focus the encompassing node.
focusNode(vvIdDiv);
// Run JS code to update the text.
executeJS("updateText()");
// Signal end of test.
mActivityTestRule.sendEndOfTestSignal();
// Check whether the text of the encompassing node has been updated.
mNodeInfo = createAccessibilityNodeInfo(vvIdDiv);
Assert.assertEquals(CACHING_ERROR, "Example text 2", mNodeInfo.getText());
}
/**
* Test our internal cache of |AccessibilityNodeInfo| objects for updates to the
* bounding boxes of nodes during window resizes.
*/
@Test
@SmallTest
public void testNodeInfoCache_BoundingBoxUpdatesOnWindowResize() {
// Build a simple web page with a flex and a will-change: transform button.
setupTestWithHTML("<div style=\"display: flex; min-height: 90vh;\">\n"
+ " <div style=\"display: flex; flex-grow: 1; align-items: flex-end;\">\n"
+ " <div>\n"
+ " <button style=\"display: inline-flex; will-change: transform;\">\n"
+ " Next\n"
+ " </button>\n"
+ " </div>\n"
+ " </div>\n"
+ "</div>");
// Find the button and get the current bounding box.
int buttonvvId = waitForNodeMatching(sClassNameMatcher, "android.widget.Button");
mNodeInfo = createAccessibilityNodeInfo(buttonvvId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertEquals(NODE_TIMEOUT_ERROR, "Next", mNodeInfo.getText());
Rect beforeBounds = new Rect();
mNodeInfo.getBoundsInScreen(beforeBounds);
// Resize the web contents.
TestThreadUtils.runOnUiThreadBlocking(
() -> mActivityTestRule.getWebContents().setSize(1080, beforeBounds.top / 3));
// Send end of test signal.
mActivityTestRule.sendEndOfTestSignal();
// Fetch the bounding box again and assert top has shrunk by at least half.
mNodeInfo = createAccessibilityNodeInfo(buttonvvId);
Rect afterBounds = new Rect();
mNodeInfo.getBoundsInScreen(afterBounds);
Assert.assertTrue(BOUNDING_BOX_ERROR, afterBounds.top < (beforeBounds.top / 2));
}
// ------------------ Tests of AccessibilityEvents ------------------ //
// These tests are included here rather than in WebContentsAccessibilityEventsTest because
// they test the AccessibilityEvent over a series of actions, rather than one method.
/**
* Ensure that disabled comboboxes and children are not shadow clickable.
*/
@Test
@SmallTest
public void testEvent_Combobox_disabled() throws Throwable {
// Build a simple web page with a disabled combobox.
setupTestWithHTML("<select disabled>\n"
+ " <option>Volvo</option>\n"
+ " <option>Saab</option>\n"
+ " <option>Mercedes</option>\n"
+ "</select>");
// Find the disabled option node and set a delegate to track focus.
int disabledNodeId = waitForNodeMatching(sTextMatcher, "Volvo");
mNodeInfo = createAccessibilityNodeInfo(disabledNodeId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
focusNode(disabledNodeId);
mTestData.setReceivedAccessibilityFocusEvent(false);
// Perform a click on the node.
performActionOnUiThread(disabledNodeId, ACTION_CLICK, null);
// Signal end of test
mActivityTestRule.sendEndOfTestSignal();
// Check we did not receive any events.
Assert.assertFalse(DISABLED_COMBOBOX_ERROR, mTestData.hasReceivedAccessibilityFocusEvent());
}
/**
* Ensure traverse events and selection events are properly indexed when navigating an edit
* field by character with selection mode off
*/
@Test
@SmallTest
public void testEvent_SelectionOFF_CharacterGranularity() throws Throwable {
// Build a simple web page with an input and the text "Testing"
setupTestWithHTML("<input id=\"fn\" type=\"text\" value=\"Testing\">");
// Find a node in the accessibility tree with input type TYPE_CLASS_TEXT.
int editTextVirtualViewId =
waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(editTextVirtualViewId);
Assert.assertNotEquals(mNodeInfo, null);
focusNode(editTextVirtualViewId);
// Set granularity to CHARACTER, with selection FALSE
Bundle args = new Bundle();
args.putInt(ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, MOVEMENT_GRANULARITY_CHARACTER);
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
// Simulate swiping left (backward)
for (int i = 7; i > 0; i--) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i - 1, mTestData.getTraverseFromIndex());
Assert.assertEquals(i, mTestData.getTraverseToIndex());
Assert.assertEquals(i - 1, mTestData.getSelectionFromIndex());
Assert.assertEquals(i - 1, mTestData.getSelectionToIndex());
}
// Simulate swiping right (forward)
for (int i = 0; i < 7; i++) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i, mTestData.getTraverseFromIndex());
Assert.assertEquals(i + 1, mTestData.getTraverseToIndex());
Assert.assertEquals(i + 1, mTestData.getSelectionFromIndex());
Assert.assertEquals(i + 1, mTestData.getSelectionToIndex());
}
}
/**
* Ensure traverse events and selection events are properly indexed when navigating an edit
* field by character with selection mode on
*/
@Test
@LargeTest
public void testEvent_SelectionON_CharacterGranularity() throws Throwable {
// Build a simple web page with an input and the text "Testing"
setupTestWithHTML("<input id=\"fn\" type=\"text\" value=\"Testing\">");
// Find a node in the accessibility tree with input type TYPE_CLASS_TEXT.
int editTextVirtualViewId =
waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(editTextVirtualViewId);
Assert.assertNotEquals(mNodeInfo, null);
focusNode(editTextVirtualViewId);
// Set granularity to CHARACTER, with selection TRUE
Bundle args = new Bundle();
args.putInt(ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, MOVEMENT_GRANULARITY_CHARACTER);
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
// Simulate swiping left (backward) (adds to selections)
for (int i = 7; i > 0; i--) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i - 1, mTestData.getTraverseFromIndex());
Assert.assertEquals(i, mTestData.getTraverseToIndex());
Assert.assertEquals(7, mTestData.getSelectionFromIndex());
Assert.assertEquals(i - 1, mTestData.getSelectionToIndex());
}
// Simulate swiping right (forward) (removes from selection)
for (int i = 0; i < 7; i++) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i, mTestData.getTraverseFromIndex());
Assert.assertEquals(i + 1, mTestData.getTraverseToIndex());
Assert.assertEquals(7, mTestData.getSelectionFromIndex());
Assert.assertEquals(i + 1, mTestData.getSelectionToIndex());
}
// Turn selection mode off and traverse to beginning so we can select forwards
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
for (int i = 7; i > 0; i--) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
}
// Turn selection mode on
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
// Simulate swiping right (forward) (adds to selection)
for (int i = 0; i < 7; i++) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i, mTestData.getTraverseFromIndex());
Assert.assertEquals(i + 1, mTestData.getTraverseToIndex());
Assert.assertEquals(0, mTestData.getSelectionFromIndex());
Assert.assertEquals(i + 1, mTestData.getSelectionToIndex());
}
// Simulate swiping left (backward) (removes from selections)
for (int i = 7; i > 0; i--) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i - 1, mTestData.getTraverseFromIndex());
Assert.assertEquals(i, mTestData.getTraverseToIndex());
Assert.assertEquals(0, mTestData.getSelectionFromIndex());
Assert.assertEquals(i - 1, mTestData.getSelectionToIndex());
}
}
/**
* Ensure traverse events and selection events are properly indexed when navigating an edit
* field by word with selection mode off
*/
@Test
@SmallTest
public void testEvent_SelectionOFF_WordGranularity() throws Throwable {
// Build a simple web page with an input and the text "Testing this output is correct"
setupTestWithHTML(
"<input id=\"fn\" type=\"text\" value=\"Testing this output is correct\">");
// Find a node in the accessibility tree with input type TYPE_CLASS_TEXT.
int editTextVirtualViewId =
waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(editTextVirtualViewId);
Assert.assertNotEquals(mNodeInfo, null);
focusNode(editTextVirtualViewId);
// Set granularity to WORD, with selection FALSE
Bundle args = new Bundle();
args.putInt(ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, MOVEMENT_GRANULARITY_WORD);
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
int[] wordStarts = new int[] {0, 8, 13, 20, 23};
int[] wordEnds = new int[] {7, 12, 19, 22, 30};
// Simulate swiping left (backward) through all 5 words, check indices along the way
for (int i = 4; i >= 0; --i) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(wordStarts[i], mTestData.getTraverseFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getTraverseToIndex());
Assert.assertEquals(wordStarts[i], mTestData.getSelectionFromIndex());
Assert.assertEquals(wordStarts[i], mTestData.getSelectionToIndex());
}
// Simulate swiping right (forward) through all 5 words, check indices along the way
for (int i = 0; i < 5; ++i) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(wordStarts[i], mTestData.getTraverseFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getTraverseToIndex());
Assert.assertEquals(wordEnds[i], mTestData.getSelectionFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getSelectionToIndex());
}
}
/**
* Ensure traverse events and selection events are properly indexed when navigating an edit
* field by word with selection mode on
*/
@Test
@LargeTest
public void testEvent_SelectionON_WordGranularity() throws Throwable {
setupTestWithHTML(
"<input id=\"fn\" type=\"text\" value=\"Testing this output is correct\">");
// Find a node in the accessibility tree with input type TYPE_CLASS_TEXT.
int editTextVirtualViewId =
waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(editTextVirtualViewId);
Assert.assertNotEquals(mNodeInfo, null);
focusNode(editTextVirtualViewId);
// Set granularity to WORD, with selection TRUE
Bundle args = new Bundle();
args.putInt(ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, MOVEMENT_GRANULARITY_WORD);
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
int[] wordStarts = new int[] {0, 8, 13, 20, 23};
int[] wordEnds = new int[] {7, 12, 19, 22, 30};
// Simulate swiping left (backward, adds to selection) through all 5 words, check indices
for (int i = 4; i >= 0; --i) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(wordStarts[i], mTestData.getTraverseFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getTraverseToIndex());
Assert.assertEquals(30, mTestData.getSelectionFromIndex());
Assert.assertEquals(wordStarts[i], mTestData.getSelectionToIndex());
}
// Simulate swiping right (forward, removes selection) through all 5 words, check indices
for (int i = 0; i < 5; ++i) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(wordStarts[i], mTestData.getTraverseFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getTraverseToIndex());
Assert.assertEquals(30, mTestData.getSelectionFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getSelectionToIndex());
}
// Turn selection mode off and traverse to beginning so we can select forwards
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
for (int i = 4; i >= 0; i--) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
}
// Turn selection mode on
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
// Simulate swiping right (forward) (adds to selection)
for (int i = 0; i < 5; ++i) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(wordStarts[i], mTestData.getTraverseFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getTraverseToIndex());
Assert.assertEquals(0, mTestData.getSelectionFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getSelectionToIndex());
}
// Simulate swiping left (backward) (removes from selections)
for (int i = 4; i >= 0; --i) {
performTextActionOnUiThread(
editTextVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(wordStarts[i], mTestData.getTraverseFromIndex());
Assert.assertEquals(wordEnds[i], mTestData.getTraverseToIndex());
Assert.assertEquals(0, mTestData.getSelectionFromIndex());
Assert.assertEquals(wordStarts[i], mTestData.getSelectionToIndex());
}
}
/**
* Ensure traverse events and selection events are properly indexed when navigating a
* contenteditable by character with selection mode on.
*/
@Test
@LargeTest
@DisabledTest(message = "https://crbug.com/1360585")
public void testEvent_contenteditable_SelectionON_CharacterGranularity() throws Throwable {
setupTestWithHTML("<div contenteditable>Testing</div>");
// Find a node in the accessibility tree with input type TYPE_CLASS_TEXT.
int contentEditableVirtualViewId =
waitForNodeMatching(sClassNameMatcher, "android.widget.EditText");
mNodeInfo = createAccessibilityNodeInfo(contentEditableVirtualViewId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
focusNode(contentEditableVirtualViewId);
// Send an end of test signal to ensure test page has fully started since some bots
// seem to flake when the page has not fully loaded before testing begins.
mActivityTestRule.sendEndOfTestSignal();
// Set granularity to CHARACTER, with selection TRUE
Bundle args = new Bundle();
args.putInt(ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, MOVEMENT_GRANULARITY_CHARACTER);
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
// Simulate swiping right (forward) (adds to selection)
for (int i = 0; i < 7; i++) {
performTextActionOnUiThread(
contentEditableVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i, mTestData.getTraverseFromIndex());
Assert.assertEquals(i + 1, mTestData.getTraverseToIndex());
Assert.assertEquals(0, mTestData.getSelectionFromIndex());
Assert.assertEquals(i + 1, mTestData.getSelectionToIndex());
}
// Simulate swiping left (backward) (removes from selections)
for (int i = 7; i > 0; i--) {
performTextActionOnUiThread(
contentEditableVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i - 1, mTestData.getTraverseFromIndex());
Assert.assertEquals(i, mTestData.getTraverseToIndex());
Assert.assertEquals(0, mTestData.getSelectionFromIndex());
Assert.assertEquals(i - 1, mTestData.getSelectionToIndex());
}
// Turn selection mode off and traverse to end so we can select backwards
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
for (int i = 7; i > 0; i--) {
performTextActionOnUiThread(
contentEditableVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
}
// Turn selection mode on
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
// Simulate swiping left (backward) (adds to selections)
for (int i = 7; i > 0; i--) {
performTextActionOnUiThread(
contentEditableVirtualViewId, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i - 1, mTestData.getTraverseFromIndex());
Assert.assertEquals(i, mTestData.getTraverseToIndex());
Assert.assertEquals(7, mTestData.getSelectionFromIndex());
Assert.assertEquals(i - 1, mTestData.getSelectionToIndex());
}
// Simulate swiping right (forward) (removes from selection)
for (int i = 0; i < 7; i++) {
performTextActionOnUiThread(
contentEditableVirtualViewId, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
Assert.assertEquals(i, mTestData.getTraverseFromIndex());
Assert.assertEquals(i + 1, mTestData.getTraverseToIndex());
Assert.assertEquals(7, mTestData.getSelectionFromIndex());
Assert.assertEquals(i + 1, mTestData.getSelectionToIndex());
}
}
/**
* Ensures paragraph navigation actions correctly navigate to the next paragraph and stop at
* the last paragraph.
*/
@Test
@SmallTest
public void testEvent_paragraphGranularity() throws Throwable {
setupTestWithHTML("<p>Paragraph 1</p>"
+ "<p>Paragraph 2</p>"
+ "<p>Paragraph 3</p>"
+ "<p>Paragraph 4</p>"
+ "<p>Paragraph 5</p>");
// Set granularity to PARAGRAPH
Bundle args = new Bundle();
args.putInt(ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, MOVEMENT_GRANULARITY_PARAGRAPH);
args.putBoolean(ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
int[] paragraphs = new int[5];
for (int i = 0; i < 5; i++) {
paragraphs[i] = waitForNodeMatching(sTextMatcher, "Paragraph " + (i + 1));
}
// Simulate swiping forward
for (int i = 0; i < 4; i++) {
mTestData.setReceivedAccessibilityFocusEvent(false);
// Perform our text selection/traversal action.
performActionOnUiThread(paragraphs[i], ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
// Poll until accessibility focus has changed
CriteriaHelper.pollUiThread(
() -> { return mTestData.hasReceivedAccessibilityFocusEvent(); });
}
// Ensure the last paragraph has accessibility focus
AccessibilityNodeInfoCompat lastParagraphNodeInfo =
createAccessibilityNodeInfo(paragraphs[4]);
Assert.assertTrue(lastParagraphNodeInfo.isAccessibilityFocused());
}
// ------------------ Tests of AccessibilityNodeInfo objects ------------------ //
// These tests are included here rather than in WebContentsAccessibilityTreeTest because
// they test the AccessibilityNodeInfo over a series of actions/events, rather than statically.
/**
* Test <input type="range"> nodes and events for incrementing/decrementing value with actions.
*/
@Test
@SmallTest
public void testNodeInfo_inputTypeRange() throws Throwable {
// Create a basic input range, and find the associated |AccessibilityNodeInfo| object.
setupTestWithHTML("<input type='range' min='0' max='40'>");
// Find the input range and assert we have the correct node.
int inputNodeVirtualViewId = waitForNodeMatching(sRangeInfoMatcher, "");
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertEquals(NODE_TIMEOUT_ERROR, 0, mNodeInfo.getRangeInfo().getMin(), 0.001);
Assert.assertEquals(NODE_TIMEOUT_ERROR, 40, mNodeInfo.getRangeInfo().getMax(), 0.001);
// Perform a series of slider increments and check results.
for (int i = 1; i <= 10; i++) {
// Increment our slider using action, and poll until we receive the scroll event.
performActionOnUiThread(inputNodeVirtualViewId, ACTION_SCROLL_FORWARD, new Bundle());
CriteriaHelper.pollUiThread(
() -> mTestData.hasReceivedEvent(), INPUT_RANGE_EVENT_ERROR);
// Refresh our node info to get the latest RangeInfo child object.
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
// Confirm slider values.
Assert.assertEquals(INPUT_RANGE_VALUE_MISMATCH, 20 + (2 * i),
mNodeInfo.getRangeInfo().getCurrent(), 0.001);
// Reset polling value for next test
mTestData.setReceivedEvent(false);
}
// Perform a series of slider decrements and check results.
for (int i = 1; i <= 20; i++) {
// Decrement our slider using action, and poll until we receive the scroll event.
performActionOnUiThread(inputNodeVirtualViewId, ACTION_SCROLL_BACKWARD, new Bundle());
CriteriaHelper.pollUiThread(
() -> mTestData.hasReceivedEvent(), INPUT_RANGE_EVENT_ERROR);
// Refresh our node info to get the latest RangeInfo child object.
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
// Confirm slider values.
Assert.assertEquals(INPUT_RANGE_VALUE_MISMATCH, 40 - (2 * i),
mNodeInfo.getRangeInfo().getCurrent(), 0.001);
// Reset polling value for next test
mTestData.setReceivedEvent(false);
}
}
/**
* Test <input type="range"> nodes and events for incrementing/decrementing value with actions.
*/
@Test
@SmallTest
public void testNodeInfo_inputTypeRangeSmall() throws Throwable {
// Create a basic input range, and find the associated |AccessibilityNodeInfo| object.
setupTestWithHTML("<input type='range' min='0' max='10' value='0'>");
// Find the input range and assert we have the correct node.
int inputNodeVirtualViewId = waitForNodeMatching(sRangeInfoMatcher, "");
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertEquals(NODE_TIMEOUT_ERROR, 0, mNodeInfo.getRangeInfo().getMin(), 0.001);
Assert.assertEquals(NODE_TIMEOUT_ERROR, 10, mNodeInfo.getRangeInfo().getMax(), 0.001);
// Perform a series of slider increments and check results.
for (int i = 1; i <= 10; i++) {
// Increment our slider using action, and poll until we receive the scroll event.
performActionOnUiThread(inputNodeVirtualViewId, ACTION_SCROLL_FORWARD, new Bundle());
CriteriaHelper.pollUiThread(
() -> mTestData.hasReceivedEvent(), INPUT_RANGE_EVENT_ERROR);
// Refresh our node info to get the latest RangeInfo child object.
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
// Confirm slider values.
Assert.assertEquals(
INPUT_RANGE_VALUE_MISMATCH, i, mNodeInfo.getRangeInfo().getCurrent(), 0.001);
// Reset polling value for next test
mTestData.setReceivedEvent(false);
}
// Perform a series of slider decrements and check results.
for (int i = 1; i <= 10; i++) {
// Decrement our slider using action, and poll until we receive the scroll event.
performActionOnUiThread(inputNodeVirtualViewId, ACTION_SCROLL_BACKWARD, new Bundle());
CriteriaHelper.pollUiThread(
() -> mTestData.hasReceivedEvent(), INPUT_RANGE_EVENT_ERROR);
// Refresh our node info to get the latest RangeInfo child object.
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
// Confirm slider values.
Assert.assertEquals(INPUT_RANGE_VALUE_MISMATCH, 10 - i,
mNodeInfo.getRangeInfo().getCurrent(), 0.001);
// Reset polling value for next test
mTestData.setReceivedEvent(false);
}
}
/**
* Test <input type="range"> nodes move by a minimum value with increment/decrement actions.
*/
@Test
@SmallTest
public void testNodeInfo_inputTypeRange_withRequiredMin() throws Throwable {
// Create a basic input range, and find the associated |AccessibilityNodeInfo| object.
setupTestWithHTML("<input type='range' min='0' max='1000' step='1'>");
// Find the input range and assert we have the correct node.
int inputNodeVirtualViewId = waitForNodeMatching(sRangeInfoMatcher, "");
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertEquals(NODE_TIMEOUT_ERROR, 0, mNodeInfo.getRangeInfo().getMin(), 0.001);
Assert.assertEquals(NODE_TIMEOUT_ERROR, 1000, mNodeInfo.getRangeInfo().getMax(), 0.001);
// Perform a series of slider increments and check results.
for (int i = 1; i <= 10; i++) {
// Increment our slider using action, and poll until we receive the scroll event.
performActionOnUiThread(inputNodeVirtualViewId, ACTION_SCROLL_FORWARD, new Bundle());
CriteriaHelper.pollUiThread(
() -> mTestData.hasReceivedEvent(), INPUT_RANGE_EVENT_ERROR);
// Refresh our node info to get the latest RangeInfo child object.
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
// Confirm slider values.
Assert.assertEquals(INPUT_RANGE_VALUE_MISMATCH, 500 + (50 * i),
mNodeInfo.getRangeInfo().getCurrent(), 0.001);
// Reset polling value for next test
mTestData.setReceivedEvent(false);
}
// Perform a series of slider decrements and check results.
for (int i = 1; i <= 20; i++) {
// Decrement our slider using action, and poll until we receive the scroll event.
performActionOnUiThread(inputNodeVirtualViewId, ACTION_SCROLL_BACKWARD, new Bundle());
CriteriaHelper.pollUiThread(
() -> mTestData.hasReceivedEvent(), INPUT_RANGE_EVENT_ERROR);
// Refresh our node info to get the latest RangeInfo child object.
mNodeInfo = createAccessibilityNodeInfo(inputNodeVirtualViewId);
// Confirm slider values.
Assert.assertEquals(INPUT_RANGE_VALUE_MISMATCH, 1000 - (50 * i),
mNodeInfo.getRangeInfo().getCurrent(), 0.001);
// Reset polling value for next test
mTestData.setReceivedEvent(false);
}
}
/**
* Test |AccessibilityNodeInfo| object for node with spelling error, and ensure the
* spelling error is encoded as a Spannable.
**/
@Test
@SmallTest
public void testNodeInfo_spellingError() {
setupTestWithHTML("<input type='text' value='one wordd has an error'>");
// Call a test API to explicitly add a spelling error in the same format as
// would be generated if spelling correction was enabled. Clear our cache for this node.
int textNodeVirtualViewId =
waitForNodeMatching(sClassNameMatcher, "android.widget.EditText");
TestThreadUtils.runOnUiThreadBlocking(() -> {
mActivityTestRule.mWcax.addSpellingErrorForTesting(textNodeVirtualViewId, 4, 9);
});
mActivityTestRule.mWcax.clearNodeInfoCacheForGivenId(textNodeVirtualViewId);
// Get |AccessibilityNodeInfo| object and confirm it is not null.
mNodeInfo = createAccessibilityNodeInfo(textNodeVirtualViewId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// Assert that the node's text has a SuggestionSpan surrounding the proper word.
CharSequence text = mNodeInfo.getText();
Assert.assertTrue(SPELLING_ERROR, text instanceof Spannable);
Spannable spannable = (Spannable) text;
SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
Assert.assertNotNull(SPELLING_ERROR, spans);
Assert.assertEquals(SPELLING_ERROR, 1, spans.length);
Assert.assertEquals(SPELLING_ERROR, 4, spannable.getSpanStart(spans[0]));
Assert.assertEquals(SPELLING_ERROR, 9, spannable.getSpanEnd(spans[0]));
}
/**
* Test |AccessibilityNodeInfo| object for character bounds for a node in Android O.
*/
@Test
@SmallTest
public void testNodeInfo_extraDataAdded_characterLocations() {
setupTestWithHTML("<h1>Simple test page</h1><section><p>Text</p></section>");
// Wait until we find a node in the accessibility tree with the text "Text".
int textNodeVirtualViewId = waitForNodeMatching(sTextMatcher, "Text");
mNodeInfo = createAccessibilityNodeInfo(textNodeVirtualViewId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// Call the API we want to test - addExtraDataToAccessibilityNodeInfo.
final Bundle arguments = new Bundle();
arguments.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
arguments.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, 4);
// addExtraDataToAccessibilityNodeInfo() will end up calling RenderFrameHostImpl's method
// AccessibilityPerformAction() in the C++ code, which needs to be run from the UI thread.
TestThreadUtils.runOnUiThreadBlocking(() -> {
mActivityTestRule.mNodeProvider.addExtraDataToAccessibilityNodeInfo(
textNodeVirtualViewId, mNodeInfo, EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY,
arguments);
});
// It should return a result, but all of the rects will be the same because it hasn't
// loaded inline text boxes yet.
Bundle extras = mNodeInfo.getExtras();
RectF[] result =
(RectF[]) extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
Assert.assertNotEquals(result, null);
Assert.assertEquals(result.length, 4);
Assert.assertEquals(result[0], result[1]);
Assert.assertEquals(result[0], result[2]);
Assert.assertEquals(result[0], result[3]);
// The role string should be a camel cased programmatic identifier.
CharSequence roleString = extras.getCharSequence(EXTRAS_KEY_CHROME_ROLE);
Assert.assertEquals("paragraph", roleString.toString());
// The data needed for text character locations loads asynchronously. Block until
// it successfully returns the character bounds.
CriteriaHelper.pollUiThread(() -> {
AccessibilityNodeInfoCompat textNode =
createAccessibilityNodeInfo(textNodeVirtualViewId);
mActivityTestRule.mNodeProvider.addExtraDataToAccessibilityNodeInfo(
textNodeVirtualViewId, textNode, EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY,
arguments);
Bundle textNodeExtras = textNode.getExtras();
RectF[] textNodeResults = (RectF[]) textNodeExtras.getParcelableArray(
EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
Criteria.checkThat(textNodeResults, Matchers.arrayWithSize(4));
Criteria.checkThat(textNodeResults[0], Matchers.not(textNodeResults[1]));
});
// The final result should be the separate bounding box of all four characters.
mNodeInfo = createAccessibilityNodeInfo(textNodeVirtualViewId);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mActivityTestRule.mNodeProvider.addExtraDataToAccessibilityNodeInfo(
textNodeVirtualViewId, mNodeInfo, EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY,
arguments);
});
extras = mNodeInfo.getExtras();
result = (RectF[]) extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
Assert.assertNotEquals(result[0], result[1]);
Assert.assertNotEquals(result[0], result[2]);
Assert.assertNotEquals(result[0], result[3]);
// All four should have nonzero left, top, width, and height
for (int i = 0; i < 4; ++i) {
Assert.assertTrue(result[i].left > 0);
Assert.assertTrue(result[i].top > 0);
Assert.assertTrue(result[i].width() > 0);
Assert.assertTrue(result[i].height() > 0);
}
// They should be in order.
Assert.assertTrue(result[0].left < result[1].left);
Assert.assertTrue(result[1].left < result[2].left);
Assert.assertTrue(result[2].left < result[3].left);
}
/**
* Test |AccessibilityNodeInfo| object for image data for a node in Android O.
*/
@Test
@SmallTest
public void testNodeInfo_extraDataAdded_imageData() {
// Setup test page with example image (20px red square).
setupTestWithHTML("<img id='id1' src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEU"
+ "gAAABQAAAAUCAIAAAAC64paAAAAGElEQVR4AWOsZiAfDLDmUc2jmk"
+ "c1j2oGADloCbFEqE6LAAAAAElFTkSuQmCC\"/>");
// Find the image node.
int imageViewId = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
mNodeInfo = createAccessibilityNodeInfo(imageViewId);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// There should be no image data in the node currently.
Assert.assertFalse(
NODE_TIMEOUT_ERROR, mNodeInfo.getExtras().containsKey(EXTRAS_KEY_IMAGE_DATA));
// The image data is added asynchronously, call the API and poll until it has been added.
CriteriaHelper.pollUiThread(() -> {
mActivityTestRule.mNodeProvider.addExtraDataToAccessibilityNodeInfo(
imageViewId, mNodeInfo, EXTRAS_DATA_REQUEST_IMAGE_DATA_KEY, new Bundle());
return mNodeInfo.getExtras().containsKey(EXTRAS_KEY_IMAGE_DATA);
});
// Verify a byte array of sufficient size has been added to the node.
Assert.assertTrue(IMAGE_DATA_BUNDLE_EXTRA_ERROR,
mNodeInfo.getExtras().containsKey(EXTRAS_KEY_IMAGE_DATA));
Assert.assertNotNull(IMAGE_DATA_BUNDLE_EXTRA_ERROR,
mNodeInfo.getExtras().getByteArray(EXTRAS_KEY_IMAGE_DATA));
Assert.assertTrue(IMAGE_DATA_BUNDLE_EXTRA_ERROR,
mNodeInfo.getExtras().getByteArray(EXTRAS_KEY_IMAGE_DATA).length > 50);
}
@Test
@SmallTest
public void testNodeInfo_extras_unclippedBounds() throws Throwable {
// Build a simple web page with a scrollable view.
setupTestFromFile("content/test/data/android/scroll_element_offscreen.html");
// Find the <div> that contains example paragraphs that can be scrolled.
int vvIdDiv = waitForNodeMatching(sViewIdResourceNameMatcher, "scroll_view");
mNodeInfo = createAccessibilityNodeInfo(vvIdDiv);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// The page may take a moment to finish onload method, so poll for a child count.
CriteriaHelper.pollUiThread(() -> {
return createAccessibilityNodeInfo(vvIdDiv).getChildCount() == 100;
}, NODE_TIMEOUT_ERROR);
// Focus the scroll container.
focusNode(vvIdDiv);
// Send a scroll event so some elements will be offscreen and poll for results.
performActionOnUiThread(vvIdDiv, ACTION_PAGE_UP, null, () -> {
return createAccessibilityNodeInfo(vvIdDiv).getExtras() != null
&& createAccessibilityNodeInfo(vvIdDiv).getExtras().getInt(
EXTRAS_KEY_UNCLIPPED_TOP, 1)
< 0;
});
// Signal end of test.
mActivityTestRule.sendEndOfTestSignal();
// Refresh the AccessibilityNodeInfo object for the container.
mNodeInfo = createAccessibilityNodeInfo(vvIdDiv);
// Check that the container has unclipped values set.
Assert.assertNotNull(NODE_EXTRAS_UNCLIPPED_ERROR, mNodeInfo.getExtras());
Assert.assertTrue(NODE_EXTRAS_UNCLIPPED_ERROR,
mNodeInfo.getExtras().getInt(EXTRAS_KEY_UNCLIPPED_TOP) < 0);
Assert.assertTrue(NODE_EXTRAS_UNCLIPPED_ERROR,
mNodeInfo.getExtras().getInt(EXTRAS_KEY_UNCLIPPED_BOTTOM) > 0);
}
/**
* Test |AccessibilityNodeInfo| object actions for node is specifically user scrollable,
* and not just programmatically scrollable.
*/
@Test
@SmallTest
public void testNodeInfo_Actions_OverflowHidden() throws Throwable {
// Build a simple web page with a div and overflow:hidden
setupTestWithHTML("<div title='1234' style='overflow:hidden; width: 200px; height:50px'>\n"
+ " <p>Example Paragraph 1</p>\n"
+ " <p>Example Paragraph 2</p>\n"
+ "</div>");
// Define our root node and paragraph node IDs by looking for their text.
int vvIdDiv = waitForNodeMatching(sTextMatcher, "1234");
int vvIdP1 = waitForNodeMatching(sTextMatcher, "Example Paragraph 1");
int vvIdP2 = waitForNodeMatching(sTextMatcher, "Example Paragraph 2");
// Get the |AccessibilityNodeInfo| objects for our nodes.
AccessibilityNodeInfoCompat nodeInfoDiv = createAccessibilityNodeInfo(vvIdDiv);
AccessibilityNodeInfoCompat nodeInfoP1 = createAccessibilityNodeInfo(vvIdP1);
AccessibilityNodeInfoCompat nodeInfoP2 = createAccessibilityNodeInfo(vvIdP2);
// Assert we have the correct nodes.
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoDiv);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoP1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoP2);
// Assert the scroll actions are not present in any of the objects.
assertActionsContainNoScrolls(nodeInfoDiv);
assertActionsContainNoScrolls(nodeInfoP1);
assertActionsContainNoScrolls(nodeInfoP2);
// Traverse to the next node, then re-assert.
performActionOnUiThread(vvIdDiv, ACTION_NEXT_HTML_ELEMENT, new Bundle());
assertActionsContainNoScrolls(nodeInfoDiv);
assertActionsContainNoScrolls(nodeInfoP1);
assertActionsContainNoScrolls(nodeInfoP2);
// Repeat.
performActionOnUiThread(vvIdP1, ACTION_NEXT_HTML_ELEMENT, new Bundle());
assertActionsContainNoScrolls(nodeInfoDiv);
assertActionsContainNoScrolls(nodeInfoP1);
assertActionsContainNoScrolls(nodeInfoP2);
}
/**
* Test |AccessibilityNodeInfo| object actions for node is user scrollable.
*/
@Test
@SmallTest
public void testNodeInfo_Actions_OverflowScroll() throws Throwable {
// Build a simple web page with a div and overflow:scroll
setupTestWithHTML(
"<div id='div1' title='1234' style='overflow:scroll; width: 200px; height:50px'>\n"
+ " <p id='p1'>Example Paragraph 1</p>\n"
+ " <p id='p2'>Example Paragraph 2</p>\n"
+ "</div>");
// Define our root node and paragraph node IDs by looking for their ids.
int vvIdDiv = waitForNodeMatching(sViewIdResourceNameMatcher, "div1");
int vvIdP1 = waitForNodeMatching(sViewIdResourceNameMatcher, "p1");
int vvIdP2 = waitForNodeMatching(sViewIdResourceNameMatcher, "p2");
// Get the |AccessibilityNodeInfo| objects for our nodes.
AccessibilityNodeInfoCompat nodeInfoDiv = createAccessibilityNodeInfo(vvIdDiv);
AccessibilityNodeInfoCompat nodeInfoP1 = createAccessibilityNodeInfo(vvIdP1);
AccessibilityNodeInfoCompat nodeInfoP2 = createAccessibilityNodeInfo(vvIdP2);
// Assert we have the correct nodes.
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoDiv);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoP1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, nodeInfoP2);
// Assert the scroll actions ARE present for our div node, but not the others.
Assert.assertTrue(nodeInfoDiv.getActionList().contains(ACTION_SCROLL_FORWARD));
Assert.assertTrue(nodeInfoDiv.getActionList().contains(ACTION_SCROLL_DOWN));
assertActionsContainNoScrolls(nodeInfoP1);
assertActionsContainNoScrolls(nodeInfoP2);
// Traverse to the next node, then re-assert.
performActionOnUiThread(vvIdDiv, ACTION_NEXT_HTML_ELEMENT, new Bundle());
Assert.assertTrue(nodeInfoDiv.getActionList().contains(ACTION_SCROLL_FORWARD));
Assert.assertTrue(nodeInfoDiv.getActionList().contains(ACTION_SCROLL_DOWN));
assertActionsContainNoScrolls(nodeInfoP1);
assertActionsContainNoScrolls(nodeInfoP2);
// Repeat.
performActionOnUiThread(vvIdP1, ACTION_NEXT_HTML_ELEMENT, new Bundle());
Assert.assertTrue(nodeInfoDiv.getActionList().contains(ACTION_SCROLL_FORWARD));
Assert.assertTrue(nodeInfoDiv.getActionList().contains(ACTION_SCROLL_DOWN));
assertActionsContainNoScrolls(nodeInfoP1);
assertActionsContainNoScrolls(nodeInfoP2);
}
/**
* Test that isVisibleToUser and offscreen extra are properly reflecting obscured views.
*/
@Test
@SmallTest
public void testNodeInfo_isVisibleToUser_offscreenCSS() {
// Build a simple web page with nodes that are clipped by CSS.
setupTestFromFile("content/test/data/android/hide_visible_elements_with_css.html");
// Find relevant nodes in the list.
int vvIdText1 = waitForNodeMatching(sTextMatcher, "1");
int vvIdText2 = waitForNodeMatching(sTextMatcher, "6");
int vvIdText3 = waitForNodeMatching(sTextMatcher, "9");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvIdText1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvIdText2);
AccessibilityNodeInfoCompat mNodeInfo3 = createAccessibilityNodeInfo(vvIdText3);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo3);
// Signal end of test.
mActivityTestRule.sendEndOfTestSignal();
// Check visibility of each element, all text nodes should be visible.
Assert.assertTrue(VISIBLE_TO_USER_ERROR, mNodeInfo1.isVisibleToUser());
Assert.assertTrue(VISIBLE_TO_USER_ERROR, mNodeInfo2.isVisibleToUser());
Assert.assertTrue(VISIBLE_TO_USER_ERROR, mNodeInfo3.isVisibleToUser());
// Check for offscreen Bundle extra, the second two texts should contain.
Assert.assertFalse(OFFSCREEN_BUNDLE_EXTRA_ERROR,
mNodeInfo1.getExtras().containsKey(EXTRAS_KEY_OFFSCREEN));
Assert.assertTrue(OFFSCREEN_BUNDLE_EXTRA_ERROR,
mNodeInfo2.getExtras().containsKey(EXTRAS_KEY_OFFSCREEN));
Assert.assertTrue(OFFSCREEN_BUNDLE_EXTRA_ERROR,
mNodeInfo2.getExtras().getBoolean(EXTRAS_KEY_OFFSCREEN));
Assert.assertTrue(OFFSCREEN_BUNDLE_EXTRA_ERROR,
mNodeInfo3.getExtras().containsKey(EXTRAS_KEY_OFFSCREEN));
Assert.assertTrue(OFFSCREEN_BUNDLE_EXTRA_ERROR,
mNodeInfo3.getExtras().getBoolean(EXTRAS_KEY_OFFSCREEN));
}
// ------------------ Tests of performAction method ------------------ //
/**
* Test that the performAction for ACTION_SET_TEXT works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_setText() throws Throwable {
// Build a simple web page with an input node to interact with.
setupTestWithHTML("<input type='text'><button>Button</button>");
// Find input node and button node.
int vvid = waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
int vvidButton = waitForNodeMatching(sTextMatcher, "Button");
AccessibilityNodeInfoCompat buttonNodeInfo = createAccessibilityNodeInfo(vvidButton);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, buttonNodeInfo);
// Verify that bad requests have no effect.
Assert.assertFalse(performActionOnUiThread(vvidButton, ACTION_SET_TEXT, null));
Assert.assertFalse(performActionOnUiThread(vvid, ACTION_SET_TEXT, null));
Bundle bundle = new Bundle();
bundle.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, null);
Assert.assertFalse(performActionOnUiThread(vvid, ACTION_SET_TEXT, bundle));
// Send a proper action and poll for update.
bundle.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "new text");
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SET_TEXT, bundle,
() -> !createAccessibilityNodeInfo(vvid).getText().toString().isEmpty()));
// Send of test signal and update node.
mActivityTestRule.sendEndOfTestSignal();
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify results.
Assert.assertEquals(PERFORM_ACTION_ERROR, "new text", mNodeInfo.getText().toString());
}
/**
* Test that the performAction for ACTION_SET_SELECTION works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_setSelection() throws Throwable {
// Build a simple web page with an input node to interact with.
setupTestWithHTML("<input type='text' value='test text'><button>Button</button>");
// Find input node and button node.
int vvid = waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
int vvidButton = waitForNodeMatching(sTextMatcher, "Button");
AccessibilityNodeInfoCompat buttonNodeInfo = createAccessibilityNodeInfo(vvidButton);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, buttonNodeInfo);
// Verify that a bad request has no effect.
Assert.assertFalse(performActionOnUiThread(vvidButton, ACTION_SET_SELECTION, null));
// Send a proper action and poll for update.
Bundle bundle = new Bundle();
bundle.putInt(ACTION_ARGUMENT_SELECTION_START_INT, 2);
bundle.putInt(ACTION_ARGUMENT_SELECTION_END_INT, 5);
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SET_SELECTION, bundle, () -> {
return createAccessibilityNodeInfo(vvid).getTextSelectionStart() > 0
&& createAccessibilityNodeInfo(vvid).getTextSelectionEnd() > 0;
}));
// Send of test signal and update node.
mActivityTestRule.sendEndOfTestSignal();
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify results.
Assert.assertEquals(PERFORM_ACTION_ERROR, 2, mNodeInfo.getTextSelectionStart());
Assert.assertEquals(PERFORM_ACTION_ERROR, 5, mNodeInfo.getTextSelectionEnd());
}
/**
* Test that the performAction for ACTION_CUT works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_cut() throws Throwable {
// Build a simple web page with an input field.
setupTestWithHTML("<input type='text' value='test text'>");
// Find the relevant node.
int vvid = waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// Select a given portion of the text.
Bundle bundle = new Bundle();
bundle.putInt(ACTION_ARGUMENT_SELECTION_START_INT, 2);
bundle.putInt(ACTION_ARGUMENT_SELECTION_END_INT, 7);
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SET_SELECTION, bundle, () -> {
return createAccessibilityNodeInfo(vvid).getTextSelectionStart() > 0
&& createAccessibilityNodeInfo(vvid).getTextSelectionEnd() > 0;
}));
// Perform the "cut" action, and poll for clipboard to be non-null.
ClipboardManager clipboardManager = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
return (ClipboardManager) mActivityTestRule.getActivity().getSystemService(
CLIPBOARD_SERVICE);
});
Assert.assertTrue(performActionOnUiThread(
vvid, ACTION_CUT, null, () -> clipboardManager.getPrimaryClip() != null));
// Send end of test signal and refresh input node.
mActivityTestRule.sendEndOfTestSignal();
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify text has been properly added to the clipboard.
Assert.assertNotNull(PERFORM_ACTION_ERROR, clipboardManager.getPrimaryClip());
Assert.assertEquals(
PERFORM_ACTION_ERROR, 1, clipboardManager.getPrimaryClip().getItemCount());
Assert.assertEquals(PERFORM_ACTION_ERROR, "st te",
clipboardManager.getPrimaryClip().getItemAt(0).getText().toString());
// Verify input node was changed by the cut action.
Assert.assertEquals(PERFORM_ACTION_ERROR, "text", mNodeInfo.getText().toString());
}
/**
* Test that the performAction for ACTION_COPY works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_copy() throws Throwable {
// Build a simple web page with an input field.
setupTestWithHTML("<input type='text' value='test text'>");
// Find the relevant node.
int vvid = waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// Select a given portion of the text.
Bundle bundle = new Bundle();
bundle.putInt(ACTION_ARGUMENT_SELECTION_START_INT, 2);
bundle.putInt(ACTION_ARGUMENT_SELECTION_END_INT, 7);
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SET_SELECTION, bundle, () -> {
return createAccessibilityNodeInfo(vvid).getTextSelectionStart() > 0
&& createAccessibilityNodeInfo(vvid).getTextSelectionEnd() > 0;
}));
// Perform the "copy" action, and poll for clipboard to be non-null.
ClipboardManager clipboardManager = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
return (ClipboardManager) mActivityTestRule.getActivity().getSystemService(
CLIPBOARD_SERVICE);
});
Assert.assertTrue(performActionOnUiThread(
vvid, ACTION_COPY, null, () -> clipboardManager.getPrimaryClip() != null));
// Send end of test signal and refresh input node.
mActivityTestRule.sendEndOfTestSignal();
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify text has been properly added to the clipboard.
Assert.assertNotNull(PERFORM_ACTION_ERROR, clipboardManager.getPrimaryClip());
Assert.assertEquals(
PERFORM_ACTION_ERROR, 1, clipboardManager.getPrimaryClip().getItemCount());
Assert.assertEquals(PERFORM_ACTION_ERROR, "st te",
clipboardManager.getPrimaryClip().getItemAt(0).getText().toString());
// Verify input node was not changed by the copy action.
Assert.assertEquals(PERFORM_ACTION_ERROR, "test text", mNodeInfo.getText().toString());
}
/**
* Test that the performAction for ACTION_PASTE works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_paste() throws Throwable {
// Build a simple web page with an input field.
setupTestWithHTML("<input type='text'>");
// Find the relevant node.
int vvid = waitForNodeMatching(sInputTypeMatcher, InputType.TYPE_CLASS_TEXT);
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// Add some ClipData to the ClipboardManager to paste.
TestThreadUtils.runOnUiThreadBlocking(() -> {
ClipboardManager clipboardManager =
(ClipboardManager) mActivityTestRule.getActivity().getSystemService(
CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText("test text", "test text"));
});
// Focus the input field node.
focusNode(vvid);
// Perform a paste action and poll for the text to change.
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_PASTE, null,
() -> !createAccessibilityNodeInfo(vvid).getText().toString().isEmpty()));
// Send end of test signal and update node info.
mActivityTestRule.sendEndOfTestSignal();
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify text has not been removed from the clipboard.
ClipboardManager clipboardManager =
(ClipboardManager) mActivityTestRule.getActivity().getSystemService(
CLIPBOARD_SERVICE);
Assert.assertNotNull(PERFORM_ACTION_ERROR, clipboardManager.getPrimaryClip());
Assert.assertEquals(
PERFORM_ACTION_ERROR, 1, clipboardManager.getPrimaryClip().getItemCount());
Assert.assertEquals(PERFORM_ACTION_ERROR, "test text",
clipboardManager.getPrimaryClip().getItemAt(0).getText().toString());
// Verify text has been properly pasted into the input field.
Assert.assertEquals(PERFORM_ACTION_ERROR, "test text", mNodeInfo.getText().toString());
}
/**
* Test that the performAction for ACTION_SET_SELECTION works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_setProgress() throws Throwable {
// Build a simple web page with an element that supports range values.
setupTestWithHTML("<input id='id1' type='range' min='0' max='50' value='10'>");
// Find the relevant node.
int vvid = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
// Verify that bad requests have no effect.
Assert.assertFalse(performActionOnUiThread(vvid, ACTION_SET_PROGRESS, null));
Bundle bundle = new Bundle();
Assert.assertFalse(performActionOnUiThread(vvid, ACTION_SET_PROGRESS, bundle));
// Send a proper action and poll for update.
bundle.putFloat(ACTION_ARGUMENT_PROGRESS_VALUE, 20);
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SET_PROGRESS, bundle, () -> {
return Math.abs(createAccessibilityNodeInfo(vvid).getRangeInfo().getCurrent() - 20)
< 0.01;
}));
// Update node.
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify results.
Assert.assertEquals(PERFORM_ACTION_ERROR, 20, mNodeInfo.getRangeInfo().getCurrent(), 0.01);
Assert.assertEquals(PERFORM_ACTION_ERROR, 0, mNodeInfo.getRangeInfo().getMin(), 0.01);
Assert.assertEquals(PERFORM_ACTION_ERROR, 50, mNodeInfo.getRangeInfo().getMax(), 0.01);
// Send action that exceeds max value to test clamping.
bundle.putFloat(ACTION_ARGUMENT_PROGRESS_VALUE, 55);
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SET_PROGRESS, bundle, () -> {
return Math.abs(createAccessibilityNodeInfo(vvid).getRangeInfo().getCurrent() - 50)
< 0.01;
}));
// Update node.
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify results.
Assert.assertEquals(PERFORM_ACTION_ERROR, 50, mNodeInfo.getRangeInfo().getCurrent(), 0.01);
Assert.assertEquals(PERFORM_ACTION_ERROR, 0, mNodeInfo.getRangeInfo().getMin(), 0.01);
Assert.assertEquals(PERFORM_ACTION_ERROR, 50, mNodeInfo.getRangeInfo().getMax(), 0.01);
// Send action that is less than minimum value to test clamping.
bundle.putFloat(ACTION_ARGUMENT_PROGRESS_VALUE, -5);
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SET_PROGRESS, bundle, () -> {
return Math.abs(createAccessibilityNodeInfo(vvid).getRangeInfo().getCurrent() - 0)
< 0.01;
}));
// Update node.
mNodeInfo = createAccessibilityNodeInfo(vvid);
// Verify results.
Assert.assertEquals(PERFORM_ACTION_ERROR, 0, mNodeInfo.getRangeInfo().getCurrent(), 0.01);
Assert.assertEquals(PERFORM_ACTION_ERROR, 0, mNodeInfo.getRangeInfo().getMin(), 0.01);
Assert.assertEquals(PERFORM_ACTION_ERROR, 50, mNodeInfo.getRangeInfo().getMax(), 0.01);
}
/**
* Test that the performAction for ACTION_SET_SELECTION works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_nextHtmlElement() throws Throwable {
// Build a simple web page with elements that can be traversed.
setupTestWithHTML("<p id='id1'>Example1</p><p id='id2'>Example2</p>");
// Find the relevant nodes.
int vvid1 = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
int vvid2 = waitForNodeMatching(sViewIdResourceNameMatcher, "id2");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
// Focus our first node.
focusNode(vvid1);
// Verify that bad requests have no effect.
Assert.assertFalse(performActionOnUiThread(vvid1, ACTION_NEXT_HTML_ELEMENT, null));
Bundle bundle = new Bundle();
bundle.putString(ACTION_ARGUMENT_HTML_ELEMENT_STRING, null);
Assert.assertFalse(performActionOnUiThread(vvid1, ACTION_NEXT_HTML_ELEMENT, bundle));
bundle.putString(ACTION_ARGUMENT_HTML_ELEMENT_STRING, "landmark");
Assert.assertFalse(performActionOnUiThread(vvid1, ACTION_NEXT_HTML_ELEMENT, bundle));
// Send a proper action and poll for update.
bundle.putString(ACTION_ARGUMENT_HTML_ELEMENT_STRING, "p");
Assert.assertTrue(performActionOnUiThread(vvid1, ACTION_NEXT_HTML_ELEMENT, bundle,
() -> createAccessibilityNodeInfo(vvid2).isAccessibilityFocused()));
// Send of test signal and update node.
mActivityTestRule.sendEndOfTestSignal();
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
// Verify results.
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo1.isAccessibilityFocused());
Assert.assertTrue(PERFORM_ACTION_ERROR, mNodeInfo2.isAccessibilityFocused());
}
/**
* Test that the performAction for ACTION_SET_SELECTION works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_previousHtmlElement() throws Throwable {
// Build a simple web page with elements that can be traversed.
setupTestWithHTML("<p id='id1'>Example1</p><p id='id2'>Example2</p>");
// Find the relevant nodes.
int vvid1 = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
int vvid2 = waitForNodeMatching(sViewIdResourceNameMatcher, "id2");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
// Focus our second node.
focusNode(vvid2);
// Verify that bad requests have no effect.
Assert.assertFalse(performActionOnUiThread(vvid2, ACTION_PREVIOUS_HTML_ELEMENT, null));
Bundle bundle = new Bundle();
bundle.putString(ACTION_ARGUMENT_HTML_ELEMENT_STRING, null);
Assert.assertFalse(performActionOnUiThread(vvid2, ACTION_PREVIOUS_HTML_ELEMENT, bundle));
bundle.putString(ACTION_ARGUMENT_HTML_ELEMENT_STRING, "landmark");
Assert.assertFalse(performActionOnUiThread(vvid2, ACTION_PREVIOUS_HTML_ELEMENT, bundle));
// Send a proper action and poll for update.
bundle.putString(ACTION_ARGUMENT_HTML_ELEMENT_STRING, "p");
Assert.assertTrue(performActionOnUiThread(vvid2, ACTION_PREVIOUS_HTML_ELEMENT, bundle,
() -> createAccessibilityNodeInfo(vvid1).isAccessibilityFocused()));
// Send of test signal and update node.
mActivityTestRule.sendEndOfTestSignal();
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
// Verify results.
Assert.assertTrue(PERFORM_ACTION_ERROR, mNodeInfo1.isAccessibilityFocused());
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo2.isAccessibilityFocused());
}
/**
* Test that the performAction for ACTION_ACCESSIBILITY_FOCUS works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_accessibilityFocus() throws Throwable {
// Build a simple web page with elements that can be traversed.
setupTestWithHTML("<p id='id1'>Example1</p><p id='id2'>Example2</p>");
// Find the relevant nodes.
int vvid1 = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
int vvid2 = waitForNodeMatching(sViewIdResourceNameMatcher, "id2");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
// Send an action and poll for update.
Assert.assertTrue(performActionOnUiThread(vvid1, ACTION_ACCESSIBILITY_FOCUS, null,
() -> createAccessibilityNodeInfo(vvid1).isAccessibilityFocused()));
// Update nodes and verify results.
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertTrue(PERFORM_ACTION_ERROR, mNodeInfo1.isAccessibilityFocused());
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo2.isAccessibilityFocused());
}
/**
* Test that the performAction for ACTION_CLEAR_ACCESSIBILITY_FOCUS works properly
* with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_accessibilityClearFocus() throws Throwable {
// Build a simple web page with elements that can be traversed.
setupTestWithHTML("<p id='id1'>Example1</p><p id='id2'>Example2</p>");
// Find the relevant nodes.
int vvid1 = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
int vvid2 = waitForNodeMatching(sViewIdResourceNameMatcher, "id2");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
// Send an action and poll for update.
Assert.assertTrue(performActionOnUiThread(vvid1, ACTION_ACCESSIBILITY_FOCUS, null,
() -> createAccessibilityNodeInfo(vvid1).isAccessibilityFocused()));
// Update nodes and verify results.
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertTrue(PERFORM_ACTION_ERROR, mNodeInfo1.isAccessibilityFocused());
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo2.isAccessibilityFocused());
// Clear accessibility focus from the node and verify.
Assert.assertTrue(performActionOnUiThread(vvid1, ACTION_CLEAR_ACCESSIBILITY_FOCUS, null,
() -> !createAccessibilityNodeInfo(vvid1).isAccessibilityFocused()));
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo1.isAccessibilityFocused());
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo2.isAccessibilityFocused());
}
/**
* Test that the performAction for ACTION_FOCUS works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_focus() throws Throwable {
// Build a simple web page with elements that can be focused.
setupTestWithHTML("<input type='text' id='id1'><input type='text' id='id2'>");
// Find the relevant nodes.
int vvid1 = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
int vvid2 = waitForNodeMatching(sViewIdResourceNameMatcher, "id2");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
// Send an action and poll for update.
Assert.assertTrue(performActionOnUiThread(
vvid1, ACTION_FOCUS, null, () -> createAccessibilityNodeInfo(vvid1).isFocused()));
// Update nodes and verify results.
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertTrue(PERFORM_ACTION_ERROR, mNodeInfo1.isFocused());
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo2.isFocused());
}
/**
* Test that the performAction for ACTION_CLEAR_FOCUS works properly with accessibility.
*/
@Test
@SmallTest
public void testPerformAction_clearFocus() throws Throwable {
// Build a simple web page with elements that can be focused.
setupTestWithHTML("<input type='text' id='id1'><input type='text' id='id2'>");
// Find the relevant nodes.
int vvid1 = waitForNodeMatching(sViewIdResourceNameMatcher, "id1");
int vvid2 = waitForNodeMatching(sViewIdResourceNameMatcher, "id2");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
// Send an action and poll for update.
Assert.assertTrue(performActionOnUiThread(
vvid1, ACTION_FOCUS, null, () -> createAccessibilityNodeInfo(vvid1).isFocused()));
// Update nodes and verify results.
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertTrue(PERFORM_ACTION_ERROR, mNodeInfo1.isFocused());
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo2.isFocused());
// Clear focus from the node and verify.
Assert.assertTrue(performActionOnUiThread(vvid1, ACTION_CLEAR_FOCUS, null,
() -> !createAccessibilityNodeInfo(vvid1).isFocused()));
mNodeInfo1 = createAccessibilityNodeInfo(vvid1);
mNodeInfo2 = createAccessibilityNodeInfo(vvid2);
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo1.isFocused());
Assert.assertFalse(PERFORM_ACTION_ERROR, mNodeInfo2.isFocused());
}
/**
* Test that the performAction for ACTION_SHOW_ON_SCREEN works properly with accessibility.
*/
@Test
@SmallTest
@DisabledTest(message = "https://crbug.com/1294296")
public void testPerformAction_showOnScreen() throws Throwable {
// Build a simple web page with a scrollable view.
setupTestFromFile("content/test/data/android/scroll_element_offscreen.html");
// Find a node offscreen, which should have the Bundle extra and large Bounds.
int vvid = waitForNodeMatching(sTextMatcher, "Example Text 77");
mNodeInfo = createAccessibilityNodeInfo(vvid);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo);
Assert.assertNotNull(NODE_EXTRAS_UNCLIPPED_ERROR, mNodeInfo.getExtras());
Rect originalBounds = new Rect(-1, -1, -1, -1);
mNodeInfo.getBoundsInScreen(originalBounds);
Assert.assertTrue(BOUNDING_BOX_ERROR, originalBounds.top > 0);
Assert.assertTrue(BOUNDING_BOX_ERROR, originalBounds.bottom > 0);
Assert.assertTrue(OFFSCREEN_BUNDLE_EXTRA_ERROR,
mNodeInfo.getExtras().containsKey(EXTRAS_KEY_OFFSCREEN));
// Send an action and poll for update.
Assert.assertTrue(performActionOnUiThread(vvid, ACTION_SHOW_ON_SCREEN, null, () -> {
return !createAccessibilityNodeInfo(vvid).getExtras().containsKey(EXTRAS_KEY_OFFSCREEN);
}));
mNodeInfo = createAccessibilityNodeInfo(vvid);
Rect updatedBounds = new Rect(-1, -1, -1, -1);
mNodeInfo.getBoundsInScreen(updatedBounds);
// Verify the bounds have decreased (moved up), and the offscreen extra has been removed.
Assert.assertTrue(BOUNDING_BOX_ERROR, originalBounds.top > updatedBounds.top);
Assert.assertTrue(BOUNDING_BOX_ERROR, originalBounds.bottom > updatedBounds.bottom);
Assert.assertFalse(OFFSCREEN_BUNDLE_EXTRA_ERROR,
mNodeInfo.getExtras().containsKey(EXTRAS_KEY_OFFSCREEN));
}
private void assertActionsContainNoScrolls(AccessibilityNodeInfoCompat nodeInfo) {
Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_FORWARD));
Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_BACKWARD));
Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_UP));
Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_DOWN));
Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_LEFT));
Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_RIGHT));
}
private String thresholdError(int count, int max) {
return THRESHOLD_ERROR + " Received " + count + ", but expected no more than: " + max;
}
private String lowThresholdError(int count) {
return THRESHOLD_LOW_EVENT_COUNT_ERROR + " Received " + count
+ ", but expected at least: " + UNSUPPRESSED_EXPECTED_COUNT;
}
/**
* Helper method to perform a series of events that trigger histograms being tracked.
* @throws Throwable error on focusNode
*/
private void performHistogramActions() throws Throwable {
// Find the three text nodes.
int vvId1 = waitForNodeMatching(sTextMatcher, "This is a test 1");
int vvId2 = waitForNodeMatching(sTextMatcher, "This is a test 2");
int vvId3 = waitForNodeMatching(sTextMatcher, "This is a test 3");
AccessibilityNodeInfoCompat mNodeInfo1 = createAccessibilityNodeInfo(vvId1);
AccessibilityNodeInfoCompat mNodeInfo2 = createAccessibilityNodeInfo(vvId2);
AccessibilityNodeInfoCompat mNodeInfo3 = createAccessibilityNodeInfo(vvId3);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo1);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo2);
Assert.assertNotNull(NODE_TIMEOUT_ERROR, mNodeInfo3);
// Focus each node in turn to generate events.
focusNode(vvId1);
focusNode(vvId2);
focusNode(vvId3);
// Signal end of test.
mActivityTestRule.sendEndOfTestSignal();
// Force recording of UMA histograms.
mActivityTestRule.mWcax.forceRecordUMAHistogramsForTesting();
mActivityTestRule.mWcax.forceRecordCacheUMAHistogramsForTesting();
}
}