1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "gtest/gtest.h"
16 #include "widget_operator.h"
17
18 using namespace OHOS::uitest;
19 using namespace std;
20
21 static constexpr auto ATTR_TEXT = "text";
22 // record the triggered touch events.
23
24 static std::unique_ptr<PointerMatrix> touch_event_records = nullptr;
25
26 class MockController2 : public UiController {
27 public:
MockController2()28 explicit MockController2() : UiController("mock_controller") {}
29
30 ~MockController2() = default;
31
SetDomFrame(string_view domFrame)32 void SetDomFrame(string_view domFrame)
33 {
34 mockDomFrames_.clear();
35 mockDomFrames_.emplace_back(string(domFrame));
36 frameIndex_ = 0;
37 }
38
SetDomFrames(vector<string> domFrames)39 void SetDomFrames(vector<string> domFrames)
40 {
41 mockDomFrames_.clear();
42 mockDomFrames_ = move(domFrames);
43 frameIndex_ = 0;
44 }
45
GetConsumedDomFrameCount() const46 uint32_t GetConsumedDomFrameCount() const
47 {
48 return frameIndex_;
49 }
50
GetUiHierarchy(vector<pair<Window,nlohmann::json>> & out)51 void GetUiHierarchy(vector<pair<Window, nlohmann::json>>& out) override
52 {
53 uint32_t newIndex = frameIndex_;
54 frameIndex_++;
55 auto winInfo = Window(0);
56 auto dom = nlohmann::json();
57 if (newIndex >= mockDomFrames_.size()) {
58 dom = nlohmann::json::parse(mockDomFrames_.at(mockDomFrames_.size() - 1));
59 } else {
60 dom = nlohmann::json::parse(mockDomFrames_.at((newIndex)));
61 }
62 out.push_back(make_pair(move(winInfo), move(dom)));
63 }
64
IsWorkable() const65 bool IsWorkable() const override
66 {
67 return true;
68 }
69
InjectTouchEventSequence(const PointerMatrix & events) const70 void InjectTouchEventSequence(const PointerMatrix &events) const override
71 {
72 touch_event_records = std::make_unique<PointerMatrix>(events.GetFingers(), events.GetSteps());
73 for (uint32_t step = 0; step < events.GetSteps(); step++) {
74 for (uint32_t finger = 0; finger < events.GetFingers(); finger++) {
75 touch_event_records->PushAction(events.At(finger, step));
76 }
77 }
78 }
79
80 private:
81 vector<string> mockDomFrames_;
82 mutable uint32_t frameIndex_ = 0;
83 };
84
85 // test fixture
86 class WidgetOperatorTest : public testing::Test {
87 protected:
SetUp()88 void SetUp() override
89 {
90 touch_event_records.reset(nullptr);
91 auto mockController = make_unique<MockController2>();
92 controller_ = mockController.get();
93 UiController::RegisterController(move(mockController), Priority::MEDIUM);
94 driver_ = make_unique<UiDriver>();
95 }
96
TearDown()97 void TearDown() override
98 {
99 controller_ = nullptr;
100 UiController::RemoveAllControllers();
101 }
102
103 MockController2 *controller_ = nullptr;
104 unique_ptr<UiDriver> driver_ = nullptr;
105 UiOpArgs opt_;
106
107 ~WidgetOperatorTest() override = default;
108 };
109
TEST_F(WidgetOperatorTest,scrollSearchRetrieveSubjectWidgetFailed)110 TEST_F(WidgetOperatorTest, scrollSearchRetrieveSubjectWidgetFailed)
111 {
112 constexpr auto mockDom0 = R"({
113 "attributes": {
114 "index": "0",
115 "resource-id": "id1",
116 "bounds": "[0,0][100,100]",
117 "text": ""
118 },
119 "children": [
120 {
121 "attributes": {
122 "index": "0",
123 "resource-id": "id4",
124 "bounds": "[0,0][50,50]",
125 "text": "USB"
126 },
127 "children": []
128 }
129 ]
130 })";
131 constexpr auto mockDom1 = R"({"attributes":{"bounds":"[0,0][100,100]"},"children":[]})";
132 controller_->SetDomFrame(mockDom0);
133
134 auto error = ApiCallErr(NO_ERROR);
135 auto scrollWidgetSelector = WidgetSelector();
136 auto matcher = WidgetAttrMatcher(ATTR_TEXT, "USB", EQ);
137 scrollWidgetSelector.AddMatcher(matcher);
138 vector<unique_ptr<Widget>> widgets;
139 driver_->FindWidgets(scrollWidgetSelector, widgets, error);
140
141 ASSERT_EQ(1, widgets.size());
142
143 // mock another dom on which the scroll-widget is missing, and perform scroll-search
144 controller_->SetDomFrame(mockDom1);
145 error = ApiCallErr(NO_ERROR);
146 auto targetWidgetSelector = WidgetSelector();
147 auto wOp = WidgetOperator(*driver_, *widgets.at(0), opt_);
148 ASSERT_EQ(nullptr, wOp.ScrollFindWidget(targetWidgetSelector, error));
149 // retrieve scroll widget failed should be marked as exception
150 ASSERT_EQ(ERR_COMPONENT_LOST, error.code_);
151 ASSERT_TRUE(error.message_.find(scrollWidgetSelector.Describe()) != string::npos)
152 << "Error message should contains the scroll-widget selection description";
153 }
154
TEST_F(WidgetOperatorTest,scrollSearchTargetWidgetNotExist)155 TEST_F(WidgetOperatorTest, scrollSearchTargetWidgetNotExist)
156 {
157 constexpr auto mockDom = R"({
158 "attributes": {
159 "index": "0",
160 "resource-id": "id1",
161 "bounds": "[0,0][100,100]",
162 "text": ""
163 },
164 "children": [
165 {
166 "attributes": {
167 "index": "0",
168 "resource-id": "id4",
169 "bounds": "[0,0][50,50]",
170 "text": "USB"
171 },
172 "children": []
173 }
174 ]
175 }
176 )";
177 controller_->SetDomFrame(mockDom);
178
179 auto error = ApiCallErr(NO_ERROR);
180 auto scrollWidgetSelector = WidgetSelector();
181 auto matcher = WidgetAttrMatcher(ATTR_TEXT, "USB", EQ);
182 scrollWidgetSelector.AddMatcher(matcher);
183 vector<unique_ptr<Widget>> widgets;
184 driver_->FindWidgets(scrollWidgetSelector, widgets, error);
185
186 ASSERT_EQ(1, widgets.size());
187
188 error = ApiCallErr(NO_ERROR);
189 auto targetWidgetMatcher = WidgetAttrMatcher(ATTR_TEXT, "wyz", EQ);
190 auto targetWidgetSelector = WidgetSelector();
191 targetWidgetSelector.AddMatcher(targetWidgetMatcher);
192 auto wOp = WidgetOperator(*driver_, *widgets.at(0), opt_);
193 ASSERT_EQ(nullptr, wOp.ScrollFindWidget(targetWidgetSelector, error));
194 }
195
TEST_F(WidgetOperatorTest,scrollSearchCheckSubjectWidget)196 TEST_F(WidgetOperatorTest, scrollSearchCheckSubjectWidget)
197 {
198 constexpr auto mockDom = R"({
199 "attributes": {
200 "bounds": "[0,0][1200,2000]",
201 "text": ""
202 },
203 "children": [
204 {
205 "attributes": {
206 "bounds": "[0,200][600,1000]",
207 "text": "USB",
208 "type": "List"
209 },
210 "children": []
211 }
212 ]
213 }
214 )";
215 controller_->SetDomFrame(mockDom);
216
217 auto error = ApiCallErr(NO_ERROR);
218 auto scrollWidgetSelector = WidgetSelector();
219 auto matcher = WidgetAttrMatcher(ATTR_TEXT, "USB", EQ);
220 scrollWidgetSelector.AddMatcher(matcher);
221 vector<unique_ptr<Widget>> images;
222 driver_->FindWidgets(scrollWidgetSelector, images, error);
223
224 ASSERT_EQ(1, images.size());
225
226 error = ApiCallErr(NO_ERROR);
227 auto targetWidgetMatcher = WidgetAttrMatcher(ATTR_TEXT, "wyz", EQ);
228 auto targetWidgetSelector = WidgetSelector();
229 targetWidgetSelector.AddMatcher(targetWidgetMatcher);
230 opt_.scrollWidgetDeadZone_ = 0; // set deadzone to 0 for easy computation
231 auto wOp = WidgetOperator(*driver_, *images.at(0), opt_);
232 ASSERT_EQ(nullptr, wOp.ScrollFindWidget(targetWidgetSelector, error));
233 // check the scroll action events, should be acted on the subject node specified by WidgetMatcher
234 ASSERT_TRUE(!touch_event_records->Empty());
235 auto &firstEvent = touch_event_records->At(0, 0);
236 auto fin = touch_event_records->GetFingers() - 1;
237 auto ste = touch_event_records->GetSteps() - 1;
238 auto &lastEvent = touch_event_records->At(fin, ste);
239 // check scroll event pointer_x
240 int32_t subjectCx = (0 + 600) / 2;
241 ASSERT_NEAR(firstEvent.point_.px_, subjectCx, 5);
242 ASSERT_NEAR(lastEvent.point_.px_, subjectCx, 5);
243
244 // check scroll event pointer_y
245 constexpr int32_t subjectWidgetHeight = 1000 - 200;
246 int32_t maxCy = 0;
247 int32_t minCy = 1E5;
248 for (uint32_t finger = 0; finger < touch_event_records->GetFingers(); finger++) {
249 for (uint32_t step = 0; step < touch_event_records->GetSteps(); step++) {
250 if (touch_event_records->At(finger, step).point_.py_ > maxCy) {
251 maxCy = touch_event_records->At(finger, step).point_.py_;
252 }
253 if (touch_event_records->At(finger, step).point_.py_ < minCy) {
254 minCy = touch_event_records->At(finger, step).point_.py_;
255 }
256 }
257 }
258
259 int32_t scrollDistanceY = maxCy - minCy;
260 ASSERT_TRUE(abs(scrollDistanceY - subjectWidgetHeight) < 5);
261 }
262
TEST_F(WidgetOperatorTest,scrollSearchCheckDirection)263 TEST_F(WidgetOperatorTest, scrollSearchCheckDirection)
264 {
265 constexpr auto mockDom = R"({
266 "attributes": {
267 "bounds": "[0,0][100,100]",
268 "text": ""
269 },
270 "children": [
271 {
272 "attributes": {
273 "bounds": "[0,0][50,50]",
274 "text": "USB",
275 "type": "List"
276 },
277 "children": []
278 }]})";
279 controller_->SetDomFrame(mockDom);
280
281 auto error = ApiCallErr(NO_ERROR);
282 auto scrollWidgetSelector = WidgetSelector();
283 auto matcher = WidgetAttrMatcher(ATTR_TEXT, "USB", EQ);
284 scrollWidgetSelector.AddMatcher(matcher);
285 vector<unique_ptr<Widget>> widgets;
286 driver_->FindWidgets(scrollWidgetSelector, widgets, error);
287 ASSERT_EQ(1, widgets.size());
288
289 error = ApiCallErr(NO_ERROR);
290 auto targetWidgetMatcher = WidgetAttrMatcher(ATTR_TEXT, "wyz", EQ);
291 auto targetWidgetSelector = WidgetSelector();
292 targetWidgetSelector.AddMatcher(targetWidgetMatcher);
293 opt_.scrollWidgetDeadZone_ = 0; // set deadzone to 0 for easy computation
294 auto wOp = WidgetOperator(*driver_, *widgets.at(0), opt_);
295 ASSERT_EQ(nullptr, wOp.ScrollFindWidget(targetWidgetSelector, error));
296 // check the scroll action events, should be acted on the specified node
297 ASSERT_TRUE(!touch_event_records->Empty());
298 // should scroll-search upward (cy_from<cy_to) then downward (cy_from>cy_to)
299 uint32_t maxCyEventIndex = 0;
300 uint32_t index = 0;
301 for (uint32_t event = 0; event < touch_event_records->GetSize() - 1; event++) {
302 if (touch_event_records->At(0, event).point_.py_ > touch_event_records->At(0, maxCyEventIndex).point_.py_) {
303 maxCyEventIndex = index;
304 }
305 index++;
306 }
307
308 for (uint32_t idx = 0; idx < touch_event_records->GetSize() - 1; idx++) {
309 if (idx < maxCyEventIndex) {
310 ASSERT_LT(touch_event_records->At(0, idx).point_.py_, touch_event_records->At(0, idx + 1).point_.py_);
311 } else if (idx > maxCyEventIndex) {
312 ASSERT_GT(touch_event_records->At(0, idx).point_.py_, touch_event_records->At(0, idx + 1).point_.py_);
313 }
314 }
315 }
316
317 /**
318 * The expected scroll-search count. The search starts upward till the target widget
319 * is found or reach the top (DOM snapshot becomes frozen); then search downward till
320 * the target widget is found or reach the bottom (DOM snapshot becomes frozen); */
TEST_F(WidgetOperatorTest,scrollSearchCheckCount_targetNotExist)321 TEST_F(WidgetOperatorTest, scrollSearchCheckCount_targetNotExist)
322 {
323 auto error = ApiCallErr(NO_ERROR);
324 // mocked widget text
325 const vector<string> domFrameSet[4] = {
326 {
327 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
328 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
329 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
330 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
331 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})"
332 },
333 {
334 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
335 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
336 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
337 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
338 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})"
339 },
340 {
341 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
342 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
343 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WLJ"},"children":[]})",
344 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
345 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})"
346 },
347 {
348 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
349 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WLJ"},"children":[]})",
350 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WLJ"},"children":[]})",
351 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
352 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})"
353 }
354 };
355
356 controller_->SetDomFrames(domFrameSet[0]); // set frame, let the scroll-widget be found firstly
357 auto scrollWidgetSelector = WidgetSelector();
358 auto matcher = WidgetAttrMatcher(ATTR_TEXT, "USB", EQ);
359 scrollWidgetSelector.AddMatcher(matcher);
360 vector<unique_ptr<Widget>> widgets;
361 driver_->FindWidgets(scrollWidgetSelector, widgets, error);
362
363 ASSERT_EQ(1, widgets.size());
364
365 auto targetWidgetMatcher = WidgetAttrMatcher(ATTR_TEXT, "xyz", EQ); // widget that will never be found
366 auto targetWidgetSelector = WidgetSelector();
367 targetWidgetSelector.AddMatcher(targetWidgetMatcher);
368
369 const uint32_t expectedSearchCount[] = {3, 4, 5, 5};
370 opt_.scrollWidgetDeadZone_ = 0; // set deadzone to 0 for easy computation
371 for (size_t index = 0; index < 4; index++) {
372 controller_->SetDomFrames(domFrameSet[index]);
373 // check search result
374 auto wOp = WidgetOperator(*driver_, *widgets.at(0), opt_);
375 ASSERT_EQ(nullptr, wOp.ScrollFindWidget(targetWidgetSelector, error));
376 // check scroll-search count
377 ASSERT_EQ(expectedSearchCount[index], controller_->GetConsumedDomFrameCount()) << index;
378 }
379 }
380
TEST_F(WidgetOperatorTest,scrollSearchCheckCount_targetExist)381 TEST_F(WidgetOperatorTest, scrollSearchCheckCount_targetExist)
382 {
383 auto error = ApiCallErr(NO_ERROR);
384 const vector<string> domFrameSet[4] = {
385 {
386 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
387 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
388 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
389 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
390 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})"
391 },
392 {
393 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
394 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
395 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WLJ"},"children":[]})",
396 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"XYZ"},"children":[]})",
397 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})"
398 },
399 {
400 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
401 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
402 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
403 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
404 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})"
405 },
406 {
407 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"USB"},"children":[]})",
408 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"XYZ"},"children":[]})",
409 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WLJ"},"children":[]})",
410 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})",
411 R"({"attributes":{"bounds":"[0,0][100,100]","hashcode":"123","type":"List","text":"WYZ"},"children":[]})"
412 }
413 };
414
415 controller_->SetDomFrames(domFrameSet[1]); // set frame, let the scroll-widget be found firstly
416 auto scrollWidgetSelector = WidgetSelector();
417 auto matcher = WidgetAttrMatcher(ATTR_TEXT, "USB", EQ);
418 scrollWidgetSelector.AddMatcher(matcher);
419 vector<unique_ptr<Widget>> widgets;
420 driver_->FindWidgets(scrollWidgetSelector, widgets, error);
421 ASSERT_EQ(1, widgets.size());
422
423 auto targetWidgetMatcher = WidgetAttrMatcher(ATTR_TEXT, "WYZ", EQ);
424 auto targetWidgetSelector = WidgetSelector();
425 targetWidgetSelector.AddMatcher(targetWidgetMatcher);
426
427 const uint32_t expectedSearchCount[] = {1, 2, 3, 4};
428 for (size_t index = 0; index < 4; index++) {
429 controller_->SetDomFrames(domFrameSet[index]);
430 // check search result
431 auto wOp = WidgetOperator(*driver_, *widgets.at(0), opt_);
432 ASSERT_NE(nullptr, wOp.ScrollFindWidget(targetWidgetSelector, error));
433 ASSERT_EQ("NONE", widgets.at(0)->GetHostTreeId()); // should return dettached widget
434 // check scroll-search count
435 ASSERT_EQ(expectedSearchCount[index], controller_->GetConsumedDomFrameCount()) << index;
436 }
437 }