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 18 namespace OHOS::uitest { 19 using namespace std; 20 using namespace nlohmann; 21 22 static constexpr float SCROLL_MOVE_FACTOR = 0.7; 23 IsScrolledToBorder(int oriDis, const std::vector<unique_ptr<Widget>> &allWidgets, const unique_ptr<Widget> &anchorLeafWidget)24 static bool IsScrolledToBorder(int oriDis, 25 const std::vector<unique_ptr<Widget>> &allWidgets, 26 const unique_ptr<Widget> &anchorLeafWidget) 27 { 28 if (oriDis < 0) { 29 return false; 30 } 31 size_t index = 0; 32 for (; index < allWidgets.size(); ++index) { 33 if (allWidgets.at(index)->GetAttr(UiAttr::ACCESSIBILITY_ID) == 34 anchorLeafWidget->GetAttr(UiAttr::ACCESSIBILITY_ID)) { 35 return std::abs(allWidgets.at(index)->GetBounds().top_ - anchorLeafWidget->GetBounds().top_) < 36 oriDis * SCROLL_MOVE_FACTOR; 37 } 38 } 39 return false; 40 } 41 ConstructNoFilterInWidgetSelector(WidgetSelector &scrollSelector, const std::string &hostApp, const std::string &hashCode)42 static void ConstructNoFilterInWidgetSelector(WidgetSelector &scrollSelector, 43 const std::string &hostApp, 44 const std::string &hashCode) 45 { 46 WidgetSelector parentStrategy; 47 WidgetMatchModel anchorModel2{UiAttr::HASHCODE, hashCode, ValueMatchPattern::EQ}; 48 parentStrategy.AddMatcher(anchorModel2); 49 scrollSelector.AddAppLocator(hostApp); 50 auto error = ApiCallErr(NO_ERROR); 51 scrollSelector.AddParentLocator(parentStrategy, error); 52 scrollSelector.SetWantMulti(true); 53 } 54 CalcFirstLeafWithInScroll(const std::vector<unique_ptr<Widget>>& widgetsInScroll)55 static int CalcFirstLeafWithInScroll(const std::vector<unique_ptr<Widget>>& widgetsInScroll) 56 { 57 std::string parentHie = "ROOT"; 58 int index = -1; 59 for (auto &tempWid : widgetsInScroll) { 60 const std::string &curHie = tempWid->GetHierarchy(); 61 if (curHie.find(parentHie) == std::string::npos) { 62 break; 63 } 64 parentHie = curHie; 65 ++index; 66 } 67 return index; 68 } 69 ConstructScrollFindSelector(const WidgetSelector &selector, const string &hashcode, const string &appName, ApiCallErr &error)70 static WidgetSelector ConstructScrollFindSelector(const WidgetSelector &selector, const string &hashcode, 71 const string &appName, ApiCallErr &error) 72 { 73 WidgetSelector newSelector = selector; 74 WidgetMatchModel anchorModel{UiAttr::HASHCODE, hashcode, ValueMatchPattern::EQ}; 75 WidgetSelector parentSelector{}; 76 parentSelector.AddMatcher(anchorModel); 77 newSelector.AddParentLocator(parentSelector, error); 78 newSelector.AddAppLocator(appName); 79 newSelector.SetWantMulti(false); 80 return newSelector; 81 } 82 WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options)83 WidgetOperator::WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options) 84 : driver_(driver), widget_(widget), options_(options) 85 { 86 } 87 GenericClick(TouchOp op, ApiCallErr &error) const88 void WidgetOperator::GenericClick(TouchOp op, ApiCallErr &error) const 89 { 90 DCHECK(op >= TouchOp::CLICK && op <= TouchOp::DOUBLE_CLICK_P); 91 auto retrieved = driver_.RetrieveWidget(widget_, error, true); 92 if (error.code_ != NO_ERROR) { 93 return; 94 } 95 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY()); 96 auto touch = OHOS::uitest::GenericClick(op, center); 97 driver_.PerformTouch(touch, options_, error); 98 } 99 ScrollToEnd(bool toTop, ApiCallErr &error) const100 void WidgetOperator::ScrollToEnd(bool toTop, ApiCallErr &error) const 101 { 102 int turnDis = -1; 103 std::unique_ptr<Widget> lastTopLeafWidget = nullptr; 104 std::unique_ptr<Widget> lastBottomLeafWidget = nullptr; 105 while (true) { 106 auto hostApp = driver_.GetHostApp(widget_); 107 WidgetSelector selector{}; 108 ConstructNoFilterInWidgetSelector(selector, hostApp, widget_.GetAttr(UiAttr::HASHCODE)); 109 std::vector<unique_ptr<Widget>> widgetsInScroll; 110 driver_.FindWidgets(selector, widgetsInScroll, error, true); 111 if (error.code_ != NO_ERROR) { 112 LOG_E("There is error when ScrollToEnd, msg is %{public}s", error.message_.c_str()); 113 return; 114 } 115 if (widgetsInScroll.empty()) { 116 LOG_I("There is no child when ScrollToEnd"); 117 return; 118 } 119 if (toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastTopLeafWidget)) { 120 return; 121 } 122 if (!toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastBottomLeafWidget)) { 123 return; 124 } 125 if (toTop) { 126 int index = CalcFirstLeafWithInScroll(widgetsInScroll); 127 if (index < 0) { 128 LOG_E("There is error when Find Widget's fist leaf"); 129 return; 130 } 131 lastTopLeafWidget = std::move(widgetsInScroll.at(index)); 132 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 133 } else { 134 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 135 } 136 TurnPage(toTop, turnDis, error); 137 } 138 } 139 DragIntoWidget(const Widget &another, ApiCallErr &error) const140 void WidgetOperator::DragIntoWidget(const Widget &another, ApiCallErr &error) const 141 { 142 auto widgetFrom = driver_.RetrieveWidget(widget_, error); 143 if (widgetFrom == nullptr || error.code_ != NO_ERROR) { 144 return; 145 } 146 auto boundsFrom = widgetFrom->GetBounds(); 147 auto widgetTo = driver_.RetrieveWidget(another, error, false); 148 if (widgetTo == nullptr || error.code_ != NO_ERROR) { 149 return; 150 } 151 auto boundsTo = widgetTo->GetBounds(); 152 auto centerFrom = Point(boundsFrom.GetCenterX(), boundsFrom.GetCenterY()); 153 auto centerTo = Point(boundsTo.GetCenterX(), boundsTo.GetCenterY()); 154 auto touch = GenericSwipe(TouchOp::DRAG, centerFrom, centerTo); 155 driver_.PerformTouch(touch, options_, error); 156 } 157 PinchWidget(float_t scale, ApiCallErr &error) const158 void WidgetOperator::PinchWidget(float_t scale, ApiCallErr &error) const 159 { 160 auto retrieved = driver_.RetrieveWidget(widget_, error); 161 if (retrieved == nullptr || error.code_ != NO_ERROR) { 162 return; 163 } 164 auto rectBound = widget_.GetBounds(); 165 if (scale < 0) { 166 error = ApiCallErr(ERR_INVALID_INPUT, "Please input the correct scale"); 167 return; 168 } 169 auto touch = GenericPinch(rectBound, scale); 170 driver_.PerformTouch(touch, options_, error); 171 } 172 InputText(string_view text, ApiCallErr &error) const173 void WidgetOperator::InputText(string_view text, ApiCallErr &error) const 174 { 175 auto retrieved = driver_.RetrieveWidget(widget_, error); 176 if (retrieved == nullptr || error.code_ != NO_ERROR) { 177 return; 178 } 179 auto origText = retrieved->GetAttr(UiAttr::TEXT); 180 if (origText.empty() && text.empty()) { 181 return; 182 } 183 static constexpr uint32_t focusTimeMs = 500; 184 static constexpr uint32_t typeCharTimeMs = 50; 185 vector<KeyEvent> events; 186 if (!origText.empty()) { 187 for (size_t index = 0; index < origText.size(); index++) { 188 events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_DPAD_RIGHT, typeCharTimeMs}); 189 events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_DPAD_RIGHT, 0}); 190 events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_DEL, typeCharTimeMs}); 191 events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_DEL, 0}); 192 } 193 } 194 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY()); 195 auto touch = OHOS::uitest::GenericClick(TouchOp::CLICK, center); 196 driver_.PerformTouch(touch, options_, error); 197 driver_.DelayMs(focusTimeMs); // short delay to ensure focus gaining 198 auto keyActionForDelete = KeysForwarder(events); 199 driver_.TriggerKey(keyActionForDelete, options_, error); 200 driver_.DelayMs(focusTimeMs); 201 driver_.InputText(text, error); 202 } 203 ScrollFindWidget(const WidgetSelector &selector, ApiCallErr &error) const204 unique_ptr<Widget> WidgetOperator::ScrollFindWidget(const WidgetSelector &selector, ApiCallErr &error) const 205 { 206 bool scrollingUp = true; 207 int turnDis = -1; 208 std::unique_ptr<Widget> lastTopLeafWidget = nullptr; 209 std::unique_ptr<Widget> lastBottomLeafWidget = nullptr; 210 auto hostApp = driver_.GetHostApp(widget_); 211 auto newSelector = ConstructScrollFindSelector(selector, widget_.GetAttr(UiAttr::HASHCODE), hostApp, error); 212 while (true) { 213 std::vector<unique_ptr<Widget>> targetsInScroll; 214 driver_.FindWidgets(newSelector, targetsInScroll, error, true); 215 if (!targetsInScroll.empty()) { 216 return std::move(targetsInScroll.at(0)); 217 } 218 WidgetSelector scrollSelector; 219 ConstructNoFilterInWidgetSelector(scrollSelector, hostApp, widget_.GetAttr(UiAttr::HASHCODE)); 220 std::vector<unique_ptr<Widget>> widgetsInScroll; 221 driver_.FindWidgets(scrollSelector, widgetsInScroll, error, false); 222 if (error.code_ != NO_ERROR) { 223 LOG_E("There is error when Find Widget's subwidget, msg is %{public}s", error.message_.c_str()); 224 return nullptr; 225 } 226 if (widgetsInScroll.empty()) { 227 LOG_I("There is no child when Find Widget's subwidget"); 228 return nullptr; 229 } 230 if (scrollingUp && IsScrolledToBorder(turnDis, widgetsInScroll, lastTopLeafWidget)) { 231 scrollingUp = false; 232 } else if (IsScrolledToBorder(turnDis, widgetsInScroll, lastBottomLeafWidget)) { 233 LOG_W("Scroll search widget failed: %{public}s", selector.Describe().data()); 234 return nullptr; 235 } 236 if (scrollingUp) { 237 int index = CalcFirstLeafWithInScroll(widgetsInScroll); 238 if (index < 0) { 239 LOG_E("There is error when Find Widget's fist leaf"); 240 return nullptr; 241 } 242 lastTopLeafWidget = std::move(widgetsInScroll.at(index)); 243 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 244 } else { 245 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 246 } 247 TurnPage(scrollingUp, turnDis, error); 248 } 249 } 250 TurnPage(bool toTop, int &oriDistance, ApiCallErr &error) const251 void WidgetOperator::TurnPage(bool toTop, int &oriDistance, ApiCallErr &error) const 252 { 253 auto bounds = widget_.GetBounds(); 254 Point topPoint(bounds.GetCenterX(), bounds.top_); 255 Point bottomPoint(bounds.GetCenterX(), bounds.bottom_); 256 if (options_.scrollWidgetDeadZone_ > 0) { 257 topPoint.py_ += options_.scrollWidgetDeadZone_; 258 bottomPoint.py_ -= options_.scrollWidgetDeadZone_; 259 } 260 auto screenSize = driver_.GetDisplaySize(error); 261 auto gestureZone = screenSize.py_ / 20; 262 if (screenSize.py_ - bounds.bottom_ <= gestureZone) { 263 bottomPoint.py_ = bottomPoint.py_ - gestureZone; 264 } 265 auto touch = (toTop) ? GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint) 266 : GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint); 267 driver_.PerformTouch(touch, options_, error); 268 oriDistance = std::abs(topPoint.py_ - bottomPoint.py_); 269 if (toTop) { 270 LOG_I("turn page from %{public}d to %{public}d", topPoint.py_, bottomPoint.py_); 271 } else { 272 LOG_I("turn page from %{public}d to %{public}d", bottomPoint.py_, topPoint.py_); 273 } 274 } 275 } // namespace OHOS::uitest 276