summaryrefslogtreecommitdiff
path: root/base/test/android/javatests/src/org/chromium/base/test/transit/ViewConditions.java
blob: ac9f53c1c6e52c0b39457293c93c5f0e2259a895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Copyright 2023 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.base.test.transit;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.any;

import android.content.res.Resources;
import android.view.View;

import androidx.test.espresso.AmbiguousViewMatcherException;
import androidx.test.espresso.NoMatchingRootException;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.platform.app.InstrumentationRegistry;

import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;

import java.util.ArrayList;
import java.util.regex.Pattern;

/** {@link Condition}s related to Android {@link View}s. */
public class ViewConditions {
    /** Fulfilled when a single matching View exists and is displayed. */
    public static class DisplayedCondition extends ExistsCondition {
        public DisplayedCondition(Matcher<View> matcher) {
            super(allOf(matcher, isDisplayed()));
        }
    }

    /**
     * Fulfilled when a single matching View exists and is displayed, but ignored if |gate| returns
     * true.
     */
    public static class GatedDisplayedCondition extends InstrumentationThreadCondition {

        private final DisplayedCondition mDisplayedCondition;
        private final Condition mGate;

        public GatedDisplayedCondition(Matcher<View> matcher, Condition gate) {
            super();
            mDisplayedCondition = new DisplayedCondition(matcher);
            mGate = gate;
        }

        @Override
        public ConditionStatus check() throws Exception {
            ConditionStatus gateStatus = mGate.check();
            String gateMessage = gateStatus.getMessageAsGate();
            if (!gateStatus.isFulfilled()) {
                return fulfilled(gateMessage);
            }

            ConditionStatus status = mDisplayedCondition.check();
            status.amendMessage(gateMessage);
            return status;
        }

        @Override
        public String buildDescription() {
            return String.format(
                    "%s (if %s)", mDisplayedCondition.buildDescription(), mGate.buildDescription());
        }
    }

    /** Fulfilled when a single matching View exists. */
    public static class ExistsCondition extends InstrumentationThreadCondition {
        private final Matcher<View> mMatcher;
        private View mViewMatched;

        public ExistsCondition(Matcher<View> matcher) {
            super();
            this.mMatcher = matcher;
        }

        @Override
        public String buildDescription() {
            return "View: " + ViewConditions.createMatcherDescription(mMatcher);
        }

        @Override
        public ConditionStatus check() {
            ViewInteraction viewInteraction = onView(mMatcher);
            String[] message = new String[1];
            try {
                viewInteraction.perform(
                        new ViewAction() {
                            @Override
                            public Matcher<View> getConstraints() {
                                return any(View.class);
                            }

                            @Override
                            public String getDescription() {
                                return "check exists and consistent";
                            }

                            @Override
                            public void perform(UiController uiController, View view) {
                                if (mViewMatched != null && mViewMatched != view) {
                                    message[0] =
                                            String.format(
                                                    "Matched a different view, was %s, now %s",
                                                    mViewMatched, view);
                                }
                                mViewMatched = view;
                            }
                        });
                return fulfilled(message[0]);
            } catch (NoMatchingViewException
                    | NoMatchingRootException
                    | AmbiguousViewMatcherException e) {
                if (mViewMatched != null) {
                    throw new IllegalStateException(
                            String.format(
                                    "Had matched a view (%s), but now got %s",
                                    mViewMatched, e.getClass().getSimpleName()),
                            e);
                }
                return notFulfilled(e.getClass().getSimpleName());
            }
        }
    }

    /** Fulfilled when no matching Views exist and are displayed. */
    public static class NotDisplayedAnymoreCondition extends InstrumentationThreadCondition {
        private final Matcher<View> mMatcher;

        public NotDisplayedAnymoreCondition(Matcher<View> matcher) {
            super();
            mMatcher = allOf(matcher, isDisplayed());
        }

        @Override
        public String buildDescription() {
            return "No more view: " + ViewConditions.createMatcherDescription(mMatcher);
        }

        @Override
        public ConditionStatus check() {
            try {
                onView(mMatcher).check(doesNotExist());
                return fulfilled();
            } catch (AssertionError e) {
                return notFulfilled();
            }
        }
    }

    private static String getResourceName(int resId) {
        return InstrumentationRegistry.getInstrumentation()
                .getContext()
                .getResources()
                .getResourceName(resId);
    }

    /** Generates a description for the matcher that replaces raw ids with resource names. */
    private static String createMatcherDescription(Matcher<View> matcher) {
        StringDescription d = new StringDescription();
        matcher.describeTo(d);
        String description = d.toString();
        Pattern numberPattern = Pattern.compile("[0-9]+");
        java.util.regex.Matcher numberMatcher = numberPattern.matcher(description);
        ArrayList<Integer> starts = new ArrayList<>();
        ArrayList<Integer> ends = new ArrayList<>();
        ArrayList<String> resourceNames = new ArrayList<>();
        while (numberMatcher.find()) {
            int resourceId = Integer.parseInt(numberMatcher.group());
            if (resourceId > 0xFFFFFF) {
                // Build-time Android resources have ids > 0xFFFFFF
                starts.add(numberMatcher.start());
                ends.add(numberMatcher.end());
                String resourceDescription = createResourceDescription(resourceId);
                resourceNames.add(resourceDescription);
            } else {
                resourceNames.add(numberMatcher.group());
            }
        }

        if (starts.size() == 0) return description;

        String newDescription = description.substring(0, starts.get(0));
        for (int i = 0; i < starts.size(); i++) {
            newDescription += resourceNames.get(i);
            int nextStart = (i == starts.size() - 1) ? description.length() : starts.get(i + 1);
            newDescription += description.substring(ends.get(i), nextStart);
        }

        return newDescription;
    }

    private static String createResourceDescription(int possibleResourceId) {
        try {
            return getResourceName(possibleResourceId);
        } catch (Resources.NotFoundException e) {
            return String.valueOf(possibleResourceId);
        }
    }
}