• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
16 #include "widget_operator.h"
17 #include <thread>
18 
19 namespace OHOS::uitest {
20     using namespace std;
21     using namespace nlohmann;
22 
23     static constexpr float SCROLL_MOVE_FACTOR = 0.7;
24 
IsScrolledToBorder(int oriDis,const std::vector<unique_ptr<Widget>> & allWidgets,const map<string,unique_ptr<Widget>> & anchorLeafWidgets,bool vertical)25     static bool IsScrolledToBorder(int oriDis,
26                                    const std::vector<unique_ptr<Widget>> &allWidgets,
27                                    const map<string, unique_ptr<Widget>> &anchorLeafWidgets,
28                                    bool vertical)
29     {
30         if (oriDis < 0) {
31             return false;
32         }
33         int baselineValue = oriDis * SCROLL_MOVE_FACTOR;
34         size_t index = 0;
35         for (; index < allWidgets.size(); ++index) {
36             string accessibilityId = allWidgets.at(index)->GetAttr(UiAttr::ACCESSIBILITY_ID);
37 
38             auto iterator = anchorLeafWidgets.find(accessibilityId);
39             if (iterator != anchorLeafWidgets.end()) {
40                 if (vertical && abs(allWidgets.at(index)->GetBounds().top_ - iterator->second->GetBounds().top_)
41                                 >= baselineValue) {
42                     LOG_D("Swipe distance of component[%{public}s] is greater than baseline", accessibilityId.c_str());
43                     return false;
44                 } else if (!vertical &&
45                            abs(allWidgets.at(index)->GetBounds().left_ - iterator->second->GetBounds().left_) >=
46                            baselineValue) {
47                     LOG_D("Swipe distance of component[%{public}s] is greater than baseline", accessibilityId.c_str());
48                     return false;
49                 }
50             } else {
51                 LOG_D("Component[%{public}s] is invisible after swipe", accessibilityId.c_str());
52                 return false;
53             }
54         }
55         return true;
56     }
57 
ConstructNoFilterInWidgetSelector(WidgetSelector & scrollSelector,const std::string & hostApp,const std::string & hashCode)58     static void ConstructNoFilterInWidgetSelector(WidgetSelector &scrollSelector,
59                                                   const std::string &hostApp,
60                                                   const std::string &hashCode)
61     {
62         WidgetSelector parentStrategy;
63         WidgetMatchModel anchorModel2{UiAttr::HASHCODE, hashCode, ValueMatchPattern::EQ};
64         parentStrategy.AddMatcher(anchorModel2);
65         scrollSelector.AddAppLocator(hostApp);
66         auto error = ApiCallErr(NO_ERROR);
67         scrollSelector.AddParentLocator(parentStrategy, error);
68         scrollSelector.SetWantMulti(true);
69     }
70 
ConstructScrollFindSelector(const WidgetSelector & selector,const string & hashcode,const string & appName,ApiCallErr & error)71     static WidgetSelector ConstructScrollFindSelector(const WidgetSelector &selector, const string &hashcode,
72         const string &appName, ApiCallErr &error)
73     {
74         WidgetSelector newSelector = selector;
75         WidgetMatchModel anchorModel{UiAttr::HASHCODE, hashcode, ValueMatchPattern::EQ};
76         WidgetSelector parentSelector{};
77         parentSelector.AddMatcher(anchorModel);
78         newSelector.AddParentLocator(parentSelector, error);
79         newSelector.AddAppLocator(appName);
80         newSelector.SetWantMulti(false);
81         return newSelector;
82     }
83 
WidgetOperator(UiDriver & driver,const Widget & widget,const UiOpArgs & options)84     WidgetOperator::WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options)
85         : driver_(driver), widget_(widget), options_(options)
86     {
87     }
88 
GenericClick(TouchOp op,ApiCallErr & error) const89     void WidgetOperator::GenericClick(TouchOp op, ApiCallErr &error) const
90     {
91         DCHECK(op >= TouchOp::CLICK && op <= TouchOp::DOUBLE_CLICK_P);
92         auto retrieved = driver_.RetrieveWidget(widget_, error, true);
93         if (retrieved == nullptr || error.code_ != NO_ERROR) {
94             return;
95         }
96         const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY(),
97             retrieved->GetDisplayId());
98         auto touch = OHOS::uitest::GenericClick(op, center);
99         driver_.PerformTouch(touch, options_, error);
100     }
101 
ScrollToEnd(bool toTop,ApiCallErr & error) const102     void WidgetOperator::ScrollToEnd(bool toTop, ApiCallErr &error) const
103     {
104         auto retrieved = driver_.RetrieveWidget(widget_, error, true);
105         if (retrieved == nullptr || error.code_ != NO_ERROR) {
106             return;
107         }
108         int turnDis = -1;
109         map<string, unique_ptr<Widget>> lastWidgets;
110         while (true) {
111             auto hostApp = driver_.GetHostApp(widget_);
112             WidgetSelector selector{};
113             ConstructNoFilterInWidgetSelector(selector, hostApp, widget_.GetAttr(UiAttr::HASHCODE));
114             std::vector<unique_ptr<Widget>> widgetsInScroll;
115             driver_.FindWidgets(selector, widgetsInScroll, error, true);
116             if (error.code_ != NO_ERROR) {
117                 LOG_E("There is error when ScrollToEnd, msg is %{public}s", error.message_.c_str());
118                 return;
119             }
120             if (widgetsInScroll.empty()) {
121                 LOG_I("There is no child when ScrollToEnd");
122                 return;
123             }
124             if (toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastWidgets, true)) {
125                 return;
126             }
127             if (!toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastWidgets, true)) {
128                 return;
129             }
130             lastWidgets.clear();
131             for (int32_t index = 0; index < widgetsInScroll.size(); index++) {
132                 lastWidgets[widgetsInScroll[index]->GetAttr(UiAttr::ACCESSIBILITY_ID)] = move(widgetsInScroll[index]);
133             }
134             TurnPage(toTop, turnDis, true, error);
135         }
136     }
137 
DragIntoWidget(const Widget & another,ApiCallErr & error) const138     void WidgetOperator::DragIntoWidget(const Widget &another, ApiCallErr &error) const
139     {
140         auto widgetFrom = driver_.RetrieveWidget(widget_, error);
141         if (widgetFrom == nullptr || error.code_ != NO_ERROR) {
142             return;
143         }
144         auto boundsFrom = widgetFrom->GetBounds();
145         auto widgetTo = driver_.RetrieveWidget(another, error, false);
146         if (widgetTo == nullptr || error.code_ != NO_ERROR) {
147             return;
148         }
149         auto boundsTo = widgetTo->GetBounds();
150         auto centerFrom = Point(boundsFrom.GetCenterX(), boundsFrom.GetCenterY(), widgetFrom->GetDisplayId());
151         auto centerTo = Point(boundsTo.GetCenterX(), boundsTo.GetCenterY(), widgetTo->GetDisplayId());
152         auto touch = GenericSwipe(TouchOp::DRAG, centerFrom, centerTo);
153         driver_.PerformTouch(touch, options_, error);
154     }
155 
PinchWidget(float_t scale,ApiCallErr & error) const156     void WidgetOperator::PinchWidget(float_t scale, ApiCallErr &error) const
157     {
158         auto retrieved = driver_.RetrieveWidget(widget_, error);
159         if (retrieved == nullptr || error.code_ != NO_ERROR) {
160             return;
161         }
162         auto rectBound = retrieved->GetBounds();
163         if (scale < 0) {
164             error = ApiCallErr(ERR_INVALID_INPUT, "Please input the correct scale");
165             return;
166         }
167         auto touch = GenericPinch(rectBound, scale);
168         driver_.PerformTouch(touch, options_, error);
169     }
170 
InputText(string_view text,ApiCallErr & error) const171     void WidgetOperator::InputText(string_view text, ApiCallErr &error) const
172     {
173         auto retrieved = driver_.RetrieveWidget(widget_, error);
174         if (retrieved == nullptr || error.code_ != NO_ERROR) {
175             return;
176         }
177         auto origText = retrieved->GetAttr(UiAttr::TEXT);
178         if (origText.empty() && text.empty()) {
179             return;
180         }
181         static constexpr uint32_t focusTimeMs = 500;
182         static constexpr uint32_t typeCharTimeMs = 50;
183         const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY(),
184             retrieved->GetDisplayId());
185         auto touch = OHOS::uitest::GenericClick(TouchOp::CLICK, center);
186         driver_.PerformTouch(touch, options_, error);
187         driver_.DelayMs(focusTimeMs); // short delay to ensure focus gaining
188         if (!options_.inputAdditional_ && !origText.empty()) {
189             vector<KeyEvent> events;
190             events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_MOVETOEND, typeCharTimeMs});
191             events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_MOVETOEND, 0});
192             driver_.DelayMs(focusTimeMs);
193             for (size_t index = 0; index < origText.size(); index++) {
194                 events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_DEL, typeCharTimeMs});
195                 events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_DEL, 0});
196             }
197             auto keyActionForDelete = KeysForwarder(events);
198             driver_.TriggerKey(keyActionForDelete, options_, error, widget_.GetDisplayId());
199             driver_.DelayMs(focusTimeMs);
200         } else {
201             driver_.TriggerKey(MoveToEnd(), options_, error, widget_.GetDisplayId());
202             driver_.DelayMs(focusTimeMs);
203         }
204         driver_.InputText(text, error, options_, widget_.GetDisplayId());
205     }
206 
ScrollFindWidget(const WidgetSelector & selector,bool vertical,ApiCallErr & error) const207     unique_ptr<Widget> WidgetOperator::ScrollFindWidget(const WidgetSelector &selector,
208                                                         bool vertical, ApiCallErr &error) const
209     {
210         auto retrieved = driver_.RetrieveWidget(widget_, error);
211         if (retrieved == nullptr || error.code_ != NO_ERROR) {
212             return nullptr;
213         }
214         bool scrollingUp = true;
215         int turnDis = -1;
216         map<string, unique_ptr<Widget>> lastWidgets;
217         auto hostApp = driver_.GetHostApp(widget_);
218         auto newSelector = ConstructScrollFindSelector(selector, widget_.GetAttr(UiAttr::HASHCODE), hostApp, error);
219         while (true) {
220             std::vector<unique_ptr<Widget>> targetsInScroll;
221             driver_.FindWidgets(newSelector, targetsInScroll, error, true);
222             if (!targetsInScroll.empty()) {
223                 return std::move(targetsInScroll.at(0));
224             }
225             WidgetSelector scrollSelector;
226             ConstructNoFilterInWidgetSelector(scrollSelector, hostApp, widget_.GetAttr(UiAttr::HASHCODE));
227             std::vector<unique_ptr<Widget>> widgetsInScroll;
228             driver_.FindWidgets(scrollSelector, widgetsInScroll, error, false);
229             if (error.code_ != NO_ERROR) {
230                 LOG_E("There is error when Find Widget's subwidget, msg is %{public}s", error.message_.c_str());
231                 return nullptr;
232             }
233             if (widgetsInScroll.empty()) {
234                 LOG_I("There is no child when Find Widget's subwidget");
235                 return nullptr;
236             }
237             if (scrollingUp && IsScrolledToBorder(turnDis, widgetsInScroll, lastWidgets, vertical)) {
238                 scrollingUp = false;
239             } else if (IsScrolledToBorder(turnDis, widgetsInScroll, lastWidgets, vertical)) {
240                 LOG_W("Scroll search widget failed: %{public}s", selector.Describe().data());
241                 return nullptr;
242             }
243             lastWidgets.clear();
244             for (int32_t index = 0; index < widgetsInScroll.size(); index++) {
245                 lastWidgets[widgetsInScroll[index]->GetAttr(UiAttr::ACCESSIBILITY_ID)] = move(widgetsInScroll[index]);
246             }
247             TurnPage(scrollingUp, turnDis, vertical, error);
248         }
249     }
250 
CheckDeadZone(bool vertical,ApiCallErr & error)251     bool WidgetOperator::CheckDeadZone(bool vertical, ApiCallErr &error)
252     {
253         auto bounds = widget_.GetBounds();
254         int maxDeadZone;
255         if (vertical) {
256             maxDeadZone = (bounds.bottom_ - bounds.top_) / TWO;
257         } else {
258             maxDeadZone = (bounds.right_ - bounds.left_) / TWO;
259         }
260         if (options_.scrollWidgetDeadZone_ >= maxDeadZone) {
261             error = ApiCallErr(ERR_INVALID_INPUT, "The offset is too large and exceeds the widget size.");
262             return false;
263         } else {
264             return true;
265         }
266     }
TurnPage(bool toTop,int & oriDistance,bool vertical,ApiCallErr & error) const267     void WidgetOperator::TurnPage(bool toTop, int &oriDistance, bool vertical, ApiCallErr &error) const
268     {
269         auto bounds = widget_.GetBounds();
270         Point topPoint;
271         Point bottomPoint;
272         auto screenSize = driver_.GetDisplaySize(error, widget_.GetDisplayId());
273         auto gestureZone = (vertical) ? screenSize.py_ / 20 : screenSize.px_ / 20;
274         topPoint = vertical ? Point(bounds.GetCenterX(), bounds.top_) : Point(bounds.left_, bounds.GetCenterY());
275         bottomPoint = vertical ? Point(bounds.GetCenterX(), bounds.bottom_) :
276             Point(bounds.right_, bounds.GetCenterY());
277         if (vertical) {
278             if (options_.scrollWidgetDeadZone_ > 0) {
279                 topPoint.py_ += options_.scrollWidgetDeadZone_;
280                 bottomPoint.py_ -= options_.scrollWidgetDeadZone_;
281             }
282             if (abs(screenSize.py_ - bottomPoint.py_) <= gestureZone) {
283                 bottomPoint.py_ = screenSize.py_ - gestureZone;
284             }
285             if (vertical && topPoint.py_ <= gestureZone) {
286                 topPoint.py_ = gestureZone;
287             }
288         } else {
289             if (options_.scrollWidgetDeadZone_ > 0) {
290                 topPoint.px_ += options_.scrollWidgetDeadZone_;
291                 bottomPoint.px_ -= options_.scrollWidgetDeadZone_;
292             }
293             if (abs(screenSize.px_ - bottomPoint.px_) <= gestureZone) {
294                 bottomPoint.px_ = screenSize.px_ - gestureZone;
295             }
296             if (topPoint.px_ <= gestureZone) {
297                 topPoint.px_ = gestureZone;
298             }
299         }
300         topPoint.displayId_ = widget_.GetDisplayId();
301         bottomPoint.displayId_ = widget_.GetDisplayId();
302         auto touch = (toTop) ? GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint)
303                              : GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint);
304         driver_.PerformTouch(touch, options_, error);
305         static constexpr auto sliceMs = 500;
306         this_thread::sleep_for(chrono::milliseconds(sliceMs));
307         oriDistance = (vertical) ? std::abs(topPoint.py_ - bottomPoint.py_) : std::abs(topPoint.px_ - bottomPoint.px_);
308         if (vertical && toTop) {
309             LOG_I("turn page vertical from %{public}d to %{public}d", topPoint.py_, bottomPoint.py_);
310         } else if (vertical) {
311             LOG_I("turn page vertical from %{public}d to %{public}d", bottomPoint.py_, topPoint.py_);
312         } else if (toTop) {
313             LOG_I("turn page horizontal from %{public}d to %{public}d", topPoint.px_, bottomPoint.px_);
314         } else {
315             LOG_I("turn page horizontal from %{public}d to %{public}d", bottomPoint.px_, topPoint.px_);
316         }
317     }
318 } // namespace OHOS::uitest
319