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