1 /* 2 * Copyright (c) 2021-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 <future> 17 #include "ui_driver.h" 18 19 namespace OHOS::uitest { 20 using namespace std; 21 using namespace nlohmann; 22 23 static constexpr string_view DUMMY_ATTRNAME_SELECTION = "selectionDesc"; 24 UpdateUi(bool updateUiTree,ApiCallErr & error)25 void UiDriver::UpdateUi(bool updateUiTree, ApiCallErr &error) 26 { 27 UiController::InstallFromProvider(); 28 uiController_ = UiController::GetController(); 29 if (uiController_ == nullptr) { 30 LOG_E("%{public}s", "No available UiController currently"); 31 error = ApiCallErr(ERR_INTERNAL, "No available UiController currently"); 32 return; 33 } 34 if (!updateUiTree) { 35 return; 36 } 37 windows_.clear(); 38 widgetTree_ = make_unique<WidgetTree>(""); 39 vector<pair<Window, nlohmann::json>> hierarchies; 40 uiController_->GetUiHierarchy(hierarchies); 41 if (hierarchies.empty()) { 42 LOG_E("%{public}s", "Get windows failed"); 43 error = ApiCallErr(ERR_INTERNAL, "Get window nodes failed"); 44 return; 45 } 46 vector<unique_ptr<WidgetTree>> trees; 47 for (auto &hierarchy : hierarchies) { 48 auto tree = make_unique<WidgetTree>(""); 49 tree->ConstructFromDom(hierarchy.second, true); 50 auto &window = hierarchy.first; 51 windows_.push_back(move(window)); 52 trees.push_back(move(tree)); 53 } 54 WidgetTree::MergeTrees(trees, *widgetTree_); 55 } 56 57 /** Get current UI tree.*/ GetWidgetTree() const58 const WidgetTree *UiDriver::GetWidgetTree() const 59 { 60 return widgetTree_ == nullptr? nullptr : widgetTree_.get(); 61 } 62 63 /** Get current UI controller.*/ GetUiController(ApiCallErr & error)64 const UiController *UiDriver::GetUiController(ApiCallErr &error) 65 { 66 this->UpdateUi(false, error); 67 return uiController_; 68 } 69 DumpUiHierarchy(nlohmann::json & out,ApiCallErr & error)70 void UiDriver::DumpUiHierarchy(nlohmann::json &out, ApiCallErr &error) 71 { 72 UpdateUi(true, error); 73 if (error.code_ != NO_ERROR || widgetTree_ == nullptr) { 74 return; 75 } 76 widgetTree_->MarshalIntoDom(out); 77 } 78 CloneFreeWidget(const Widget & from,const WidgetSelector & selector)79 static unique_ptr<Widget> CloneFreeWidget(const Widget &from, const WidgetSelector &selector) 80 { 81 auto clone = from.Clone("NONE", from.GetHierarchy()); 82 // save the selection desc as dummy attribute 83 clone->SetAttr(DUMMY_ATTRNAME_SELECTION, selector.Describe()); 84 return clone; 85 } 86 RetrieveWidget(const Widget & widget,ApiCallErr & err,bool updateUi)87 const Widget *UiDriver::RetrieveWidget(const Widget &widget, ApiCallErr &err, bool updateUi) 88 { 89 if (updateUi) { 90 UpdateUi(true, err); 91 if (err.code_ != NO_ERROR) { 92 return nullptr; 93 } 94 } 95 // retrieve widget by hashcode or by hierarchy 96 constexpr auto attrHashCode = ATTR_NAMES[UiAttr::HASHCODE]; 97 constexpr auto attrHierarchy = ATTR_NAMES[UiAttr::HIERARCHY]; 98 auto hashcodeMatcher = WidgetAttrMatcher(attrHashCode, widget.GetAttr(attrHashCode, "NA"), EQ); 99 auto hierarchyMatcher = WidgetAttrMatcher(attrHierarchy, widget.GetHierarchy(), EQ); 100 auto anyMatcher = Any(hashcodeMatcher, hierarchyMatcher); 101 vector<reference_wrapper<const Widget>> recv; 102 auto visitor = MatchedWidgetCollector(anyMatcher, recv); 103 widgetTree_->DfsTraverse(visitor); 104 stringstream msg; 105 msg << "Widget: " << widget.GetAttr(DUMMY_ATTRNAME_SELECTION, ""); 106 msg << "dose not exist on current UI! Check if the UI has changed after you got the widget object"; 107 if (recv.empty()) { 108 msg << "(NoCandidates)"; 109 err = ApiCallErr(ERR_COMPONENT_LOST, msg.str()); 110 LOG_W("%{public}s", err.message_.c_str()); 111 return nullptr; 112 } 113 DCHECK(recv.size() == 1); 114 auto &retrieved = recv.at(0).get(); 115 // confirm type 116 constexpr auto attrType = ATTR_NAMES[UiAttr::TYPE]; 117 if (widget.GetAttr(attrType, "A").compare(retrieved.GetAttr(attrType, "B")) != 0) { 118 msg << " (CompareEqualsFailed)"; 119 err = ApiCallErr(ERR_COMPONENT_LOST, msg.str()); 120 LOG_W("%{public}s", err.message_.c_str()); 121 return nullptr; 122 } 123 return &retrieved; 124 } 125 TriggerKey(const KeyAction & key,const UiOpArgs & opt,ApiCallErr & error)126 void UiDriver::TriggerKey(const KeyAction &key, const UiOpArgs &opt, ApiCallErr &error) 127 { 128 UpdateUi(false, error); 129 if (error.code_ != NO_ERROR) { 130 return; 131 } 132 vector<KeyEvent> events; 133 key.ComputeEvents(events, opt); 134 if (events.empty()) { 135 return; 136 } 137 uiController_->InjectKeyEventSequence(events); 138 uiController_->WaitForUiSteady(opt.uiSteadyThresholdMs_, opt.waitUiSteadyMaxMs_); 139 } 140 FindWidgets(const WidgetSelector & select,vector<unique_ptr<Widget>> & rev,ApiCallErr & err,bool updateUi)141 void UiDriver::FindWidgets(const WidgetSelector &select, vector<unique_ptr<Widget>> &rev, 142 ApiCallErr &err, bool updateUi) 143 { 144 if (updateUi) { 145 UpdateUi(true, err); 146 if (err.code_ != NO_ERROR) { 147 return; 148 } 149 } 150 vector<reference_wrapper<const Widget>> widgets; 151 select.Select(*widgetTree_, widgets); 152 // covert widgets to images as return value 153 uint32_t index = 0; 154 for (auto &ref : widgets) { 155 auto image = CloneFreeWidget(ref.get(), select); 156 // at sometime, more than one widgets are found, add the node index to the description 157 auto selectionDesc = select.Describe() + "(index=" + to_string(index) + ")"; 158 image->SetAttr(DUMMY_ATTRNAME_SELECTION, selectionDesc); 159 rev.emplace_back(move(image)); 160 index++; 161 } 162 } 163 WaitForWidget(const WidgetSelector & select,const UiOpArgs & opt,ApiCallErr & err)164 unique_ptr<Widget> UiDriver::WaitForWidget(const WidgetSelector &select, const UiOpArgs &opt, ApiCallErr &err) 165 { 166 const uint32_t sliceMs = 20; 167 const auto startMs = GetCurrentMillisecond(); 168 vector<unique_ptr<Widget>> receiver; 169 do { 170 FindWidgets(select, receiver, err); 171 if (err.code_ != NO_ERROR) { // abort on error 172 return nullptr; 173 } 174 if (!receiver.empty()) { 175 return move(receiver.at(0)); 176 } 177 DelayMs(sliceMs); 178 } while (GetCurrentMillisecond() - startMs < opt.waitWidgetMaxMs_); 179 return nullptr; 180 } 181 DelayMs(uint32_t ms)182 void UiDriver::DelayMs(uint32_t ms) 183 { 184 if (ms > 0) { 185 this_thread::sleep_for(chrono::milliseconds(ms)); 186 } 187 } 188 PerformTouch(const TouchAction & touch,const UiOpArgs & opt,ApiCallErr & err)189 void UiDriver::PerformTouch(const TouchAction &touch, const UiOpArgs &opt, ApiCallErr &err) 190 { 191 UpdateUi(false, err); 192 if (err.code_ != NO_ERROR) { 193 return; 194 } 195 PointerMatrix events; 196 touch.Decompose(events, opt); 197 if (events.Empty()) { 198 return; 199 } 200 uiController_->InjectTouchEventSequence(events); 201 uiController_->WaitForUiSteady(opt.uiSteadyThresholdMs_, opt.waitUiSteadyMaxMs_); 202 } 203 TakeScreenCap(string_view savePath,ApiCallErr & err)204 void UiDriver::TakeScreenCap(string_view savePath, ApiCallErr &err) 205 { 206 UpdateUi(false, err); 207 if (err.code_ != NO_ERROR) { 208 return; 209 } 210 stringstream errorRecv; 211 if (!uiController_->TakeScreenCap(savePath, errorRecv)) { 212 string errStr = errorRecv.str(); 213 LOG_W("ScreenCap failed: %{public}s", errStr.c_str()); 214 if (errStr.find("File opening failed") == 0) { 215 err = ApiCallErr(ERR_INVALID_INPUT, "Invalid save path or permission denied"); 216 } else { 217 err = ApiCallErr(ERR_INTERNAL, errStr); 218 } 219 LOG_W("ScreenCap failed: %{public}s", errorRecv.str().c_str()); 220 } else { 221 LOG_D("ScreenCap saved to %{public}s", savePath.data()); 222 } 223 } 224 FindWindow(function<bool (const Window &)> matcher,ApiCallErr & err)225 unique_ptr<Window> UiDriver::FindWindow(function<bool(const Window &)> matcher, ApiCallErr &err) 226 { 227 UpdateUi(true, err); 228 if (err.code_ != NO_ERROR) { 229 return nullptr; 230 } 231 for (const auto &window : windows_) { 232 if (matcher(window)) { 233 auto clone = make_unique<Window>(0); 234 *clone = window; // copy construct 235 return clone; 236 } 237 } 238 return nullptr; 239 } 240 RetrieveWindow(const Window & window,ApiCallErr & err)241 const Window *UiDriver::RetrieveWindow(const Window &window, ApiCallErr &err) 242 { 243 UpdateUi(true, err); 244 if (err.code_ != NO_ERROR) { 245 return nullptr; 246 } 247 for (const auto &win : windows_) { 248 if (win.id_ == window.id_) { 249 return &win; 250 } 251 } 252 stringstream msg; 253 msg << "Window " << window.id_; 254 msg << "dose not exist on current UI! Check if the UI has changed after you got the window object"; 255 err = ApiCallErr(ERR_COMPONENT_LOST, msg.str()); 256 LOG_W("%{public}s", err.message_.c_str()); 257 return nullptr; 258 } 259 } // namespace OHOS::uitest 260