• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     class TreeSnapshotTaker : public WidgetVisitor {
24     public:
TreeSnapshotTaker(stringstream & receiver)25         explicit TreeSnapshotTaker(stringstream &receiver) : receiver_(receiver) {};
26 
~TreeSnapshotTaker()27         ~TreeSnapshotTaker() {}
28 
Visit(const Widget & widget)29         void Visit(const Widget &widget) override
30         {
31             receiver_ << widget.GetAttr(ATTR_NAMES[UiAttr::TYPE], "") << "/";
32             receiver_ << widget.GetAttr(ATTR_NAMES[UiAttr::TEXT], "") << ";";
33         }
34 
35     private:
36         stringstream &receiver_;
37     };
38 
UpdateUi(bool updateUiTree,ApiCallErr & error)39     void UiDriver::UpdateUi(bool updateUiTree, ApiCallErr &error)
40     {
41         UiController::InstallForDevice(deviceName_);
42         uiController_ = UiController::GetController(deviceName_);
43         if (uiController_ == nullptr) {
44             LOG_E("%{public}s", (string("No available UiController for device: ") + string(deviceName_)).c_str());
45             error = ApiCallErr(INTERNAL_ERROR, "No available UiController currently");
46             return;
47         }
48         if (!updateUiTree) {
49             return;
50         }
51         widgetTree_ = make_unique<WidgetTree>("");
52         auto domData = json();
53         uiController_->GetCurrentUiDom(domData);
54         widgetTree_->ConstructFromDom(domData, true);
55     }
56 
57     /**Inflate widget-image attributes from the given widget-object and the selector.*/
Widget2Image(const Widget & widget,WidgetImage & image,const WidgetSelector & selector)58     static void Widget2Image(const Widget &widget, WidgetImage &image, const WidgetSelector &selector)
59     {
60         map<string, string> attributes;
61         widget.DumpAttributes(attributes);
62         image.SetAttributes(attributes);
63         image.SetSelectionDesc(selector.Describe());
64     }
65 
RetrieveWidget(const WidgetImage & img,ApiCallErr & err,bool updateUi)66     const Widget *UiDriver::RetrieveWidget(const WidgetImage &img, ApiCallErr &err, bool updateUi)
67     {
68         if (updateUi) {
69             UpdateUi(true, err);
70             if (err.code_ != NO_ERROR) {
71                 return nullptr;
72             }
73         }
74         // retrieve widget by hashcode or by hierarchy
75         auto hashcodeMatcher = WidgetAttrMatcher(ATTR_HASHCODE, img.GetHashCode(), EQ);
76         auto hierarchyMatcher = WidgetAttrMatcher(ATTR_HIERARCHY, img.GetHierarchy(), EQ);
77         auto anyMatcher = Any(hashcodeMatcher, hierarchyMatcher);
78         vector<reference_wrapper<const Widget>> recv;
79         auto visitor = MatchedWidgetCollector(anyMatcher, recv);
80         widgetTree_->DfsTraverse(visitor);
81         stringstream msg;
82         msg << "Widget: " << img.GetSelectionDesc();
83         msg << "dose not exist on current UI! Check if the UI has changed after you go the widget object";
84         if (recv.empty()) {
85             msg << "(NoCandidates)";
86             LOG_W("%{public}s", msg.str().c_str());
87             err = ApiCallErr(WIDGET_LOST, msg.str());
88             return nullptr;
89         }
90         DCHECK(recv.size() == 1);
91         auto &widget = recv.at(0).get();
92         // check equality
93         WidgetImage newImage = WidgetImage();
94         WidgetSelector selector; // dummy selector
95         Widget2Image(widget, newImage, selector);
96         if (!img.Compare(newImage)) {
97             msg << " (CompareEqualsFailed)";
98             LOG_W("%{public}s", msg.str().c_str());
99             err = ApiCallErr(WIDGET_LOST, msg.str());
100             return nullptr;
101         }
102         return &widget;
103     }
104 
InjectGenericClick(PointerOp type,const Point & point,const UiController & controller,const UiDriveOptions & options)105     static void InjectGenericClick(PointerOp type, const Point &point, const UiController &controller,
106                                    const UiDriveOptions &options)
107     {
108         auto action = GenericClick(type);
109         vector<TouchEvent> events;
110         action.Decompose(events, point, options);
111         if (events.empty()) { return; }
112         controller.InjectTouchEventSequence(events);
113         controller.WaitForUiSteady(options.uiSteadyThresholdMs_, options.waitUiSteadyMaxMs_);
114     }
115 
InjectGenericSwipe(PointerOp type,const Point & point0,const Point & point1,const UiController & controller,const UiDriveOptions & options)116     static void InjectGenericSwipe(PointerOp type, const Point &point0, const Point &point1,
117                                    const UiController &controller, const UiDriveOptions &options)
118     {
119         auto action = GenericSwipe(type);
120         vector<TouchEvent> events;
121         action.Decompose(events, point0, point1, options);
122         if (events.empty()) { return; }
123         controller.InjectTouchEventSequence(events);
124         controller.WaitForUiSteady(options.uiSteadyThresholdMs_, options.waitUiSteadyMaxMs_);
125     }
126 
127     /**Convert WidgetOperation to PointerActions and do injection.*/
InjectWidgetOperate(const Rect & bounds,WidgetOp operate,const UiController & controller,const UiDriveOptions & options)128     static void InjectWidgetOperate(const Rect &bounds, WidgetOp operate, const UiController &controller,
129                                     const UiDriveOptions &options)
130     {
131         const int32_t cx = bounds.GetCenterX();
132         const int32_t cy = bounds.GetCenterY();
133         switch (operate) {
134             case WidgetOp::CLICK:
135                 InjectGenericClick(PointerOp::CLICK_P, {cx, cy}, controller, options);
136                 break;
137             case WidgetOp::LONG_CLICK:
138                 InjectGenericClick(PointerOp::LONG_CLICK_P, {cx, cy}, controller, options);
139                 break;
140             case WidgetOp::DOUBLE_CLICK:
141                 InjectGenericClick(PointerOp::DOUBLE_CLICK_P, {cx, cy}, controller, options);
142                 break;
143             case WidgetOp::SWIPE_L2R:
144                 InjectGenericSwipe(PointerOp::SWIPE_P, {bounds.left_, cy}, {bounds.right_, cy}, controller, options);
145                 break;
146             case WidgetOp::SWIPE_R2L:
147                 InjectGenericSwipe(PointerOp::SWIPE_P, {bounds.right_, cy}, {bounds.left_, cy}, controller, options);
148                 break;
149             case WidgetOp::SWIPE_T2B:
150                 InjectGenericSwipe(PointerOp::SWIPE_P, {cx, bounds.top_}, {cx, bounds.bottom_}, controller, options);
151                 break;
152             case WidgetOp::SWIPE_B2T:
153                 InjectGenericSwipe(PointerOp::SWIPE_P, {cx, bounds.bottom_}, {cx, bounds.top_}, controller, options);
154                 break;
155         }
156     }
157 
InjectKeyAction(const KeyAction & action,const UiController & controller,const UiDriveOptions & options)158     static void InjectKeyAction(const KeyAction &action, const UiController &controller, const UiDriveOptions &options)
159     {
160         vector<KeyEvent> events;
161         action.ComputeEvents(events, options);
162         if (events.empty()) { return; }
163         controller.InjectKeyEventSequence(events);
164         controller.WaitForUiSteady(options.uiSteadyThresholdMs_, options.waitUiSteadyMaxMs_);
165     }
166 
FindScrollWidget(const WidgetImage & img) const167     const Widget *UiDriver::FindScrollWidget(const WidgetImage &img) const
168     {
169         vector<reference_wrapper<const Widget>> recv;
170         static constexpr string_view attrType = ATTR_NAMES[UiAttr::TYPE];
171         // scrollable widget usually has unique type on a UI frame, some find it by type
172         auto typeMatcher = WidgetAttrMatcher(attrType, img.GetAttribute(attrType, ""), EQ);
173         auto visitor = MatchedWidgetCollector(typeMatcher, recv);
174         widgetTree_->DfsTraverse(visitor);
175         if (recv.empty()) {
176             return nullptr;
177         }
178         return &(recv.at(0).get());
179     }
180 
UiDriver(string_view device)181     UiDriver::UiDriver(string_view device) : deviceName_(device) {}
182 
TriggerKey(const KeyAction & action,ApiCallErr & error)183     void UiDriver::TriggerKey(const KeyAction &action, ApiCallErr &error)
184     {
185         UpdateUi(false, error);
186         if (error.code_ != NO_ERROR) {
187             return;
188         }
189         InjectKeyAction(action, *uiController_, options_);
190     }
191 
PerformWidgetOperate(const WidgetImage & image,WidgetOp type,ApiCallErr & error)192     void UiDriver::PerformWidgetOperate(const WidgetImage &image, WidgetOp type, ApiCallErr &error)
193     {
194         auto widget = RetrieveWidget(image, error);
195         if (widget == nullptr || error.code_ != NO_ERROR) {
196             return;
197         }
198         InjectWidgetOperate(widget->GetBounds(), type, *uiController_, options_);
199     }
200 
InputText(const WidgetImage & image,string_view text,ApiCallErr & error)201     void UiDriver::InputText(const WidgetImage &image, string_view text, ApiCallErr &error)
202     {
203         auto widget = RetrieveWidget(image, error);
204         if (widget == nullptr || error.code_ != NO_ERROR) {
205             return;
206         }
207         LOG_D("Injecting string '%{public}s' into widget: %{public}s", text.data(), widget->ToStr().c_str());
208 #ifdef __DOUBLE_FRAMEWORK__
209         // click on the target widget to gain focus
210         InjectWidgetOperate(widget->GetBounds(), WidgetOp::CLICK, *uiController_, options_);
211         // set text to clipboard
212         uiController_->PutTextToClipboard(text);
213         // trigger Ctrl+V to paste text
214         auto pasteKey = Paste();
215         InjectKeyAction(pasteKey, *uiController_, options_);
216 #else
217         vector<char> chars(text.begin(), text.end()); // decompose to sing-char input sequence
218         static constexpr char charDelete = 0x7F;
219         chars.insert(chars.begin(), charDelete);
220         vector<pair<int32_t, int32_t>> keyCodes;
221         for (auto ch: chars) {
222             int32_t code = KEYCODE_NONE;
223             int32_t ctrlCode = KEYCODE_NONE;
224             if (!uiController_->GetCharKeyCode(ch, code, ctrlCode)) {
225                 error = ApiCallErr(USAGE_ERROR, string("Cannot input char ") + ch);
226                 return;
227             }
228             keyCodes.emplace_back(make_pair(code, ctrlCode));
229         }
230         InjectWidgetOperate(widget->GetBounds(), WidgetOp::CLICK, *uiController_, options_);
231         static constexpr uint32_t focusTimeMs = 200;
232         static constexpr uint32_t typeCharTimeMs = 50;
233         DelayMs(focusTimeMs); // short delay to ensure focus gaining
234         vector<KeyEvent> events;
235         for (auto &pair : keyCodes) {
236             if (pair.second != KEYCODE_NONE) {
237                 events.emplace_back(KeyEvent {ActionStage::DOWN, pair.second, 0});
238             }
239             events.emplace_back(KeyEvent {ActionStage::DOWN, pair.first, typeCharTimeMs});
240             events.emplace_back(KeyEvent {ActionStage::UP, pair.first, 0});
241             if (pair.second != KEYCODE_NONE) {
242                 events.emplace_back(KeyEvent {ActionStage::UP, pair.second, 0});
243             }
244             uiController_->InjectKeyEventSequence(events);
245             events.clear();
246         }
247         uiController_->WaitForUiSteady(options_.uiSteadyThresholdMs_, options_.waitUiSteadyMaxMs_);
248 #endif
249     }
250 
TakeScopeUiSnapshot(const WidgetTree & tree,const Widget & root)251     static string TakeScopeUiSnapshot(const WidgetTree &tree, const Widget &root)
252     {
253         stringstream os;
254         TreeSnapshotTaker snapshotTaker(os);
255         tree.DfsTraverseDescendants(snapshotTaker, root);
256         return os.str();
257     }
258 
ScrollSearch(const WidgetImage & img,const WidgetSelector & selector,ApiCallErr & err,int32_t deadZoneSize)259     unique_ptr<WidgetImage> UiDriver::ScrollSearch(const WidgetImage &img, const WidgetSelector &selector,
260                                                    ApiCallErr &err, int32_t deadZoneSize)
261     {
262         vector<TouchEvent> scrollEvents;
263         bool scrollingUp = true;
264         string prevSnapshot;
265         vector<reference_wrapper<const Widget>> receiver;
266         while (true) {
267             auto scrollWidget = RetrieveWidget(img, err);
268             if (scrollWidget == nullptr) {
269                 scrollWidget = FindScrollWidget(img);
270                 if (scrollWidget != nullptr) {
271                     err = ApiCallErr(NO_ERROR);
272                 }
273             }
274             if (scrollWidget == nullptr || err.code_ != NO_ERROR) {
275                 return nullptr;
276             }
277             selector.Select(*widgetTree_, receiver);
278             if (!receiver.empty()) {
279                 auto image = make_unique<WidgetImage>();
280                 Widget2Image(receiver.at(0), *image, selector);
281                 return image;
282             }
283             string snapshot = TakeScopeUiSnapshot(*widgetTree_, *scrollWidget);
284             if (snapshot == prevSnapshot) {
285                 // scrolling down to bottom, search completed with failure
286                 if (!scrollingUp) {
287                     auto msg = string("Scroll search widget failed: ") + selector.Describe();
288                     LOG_W("%{public}s", msg.c_str());
289                     return nullptr;
290                 } else {
291                     // scrolling down to top, change direction
292                     scrollingUp = false;
293                 }
294             }
295             prevSnapshot = snapshot;
296             // execute scrolling on the scroll_widget without update UI
297             const auto type = scrollingUp ? WidgetOp::SWIPE_T2B : WidgetOp::SWIPE_B2T;
298             auto bounds = scrollWidget->GetBounds();
299             if (deadZoneSize > 0) {
300                 // scroll widget from its deadZone maybe unresponsive
301                 bounds.top_ += deadZoneSize;
302                 bounds.bottom_ -= deadZoneSize;
303             }
304             InjectWidgetOperate(bounds, type, *uiController_, options_);
305         }
306     }
307 
DragWidgetToAnother(const WidgetImage & imgFrom,const WidgetImage & imgTo,ApiCallErr & err)308     void UiDriver::DragWidgetToAnother(const WidgetImage &imgFrom, const WidgetImage &imgTo, ApiCallErr &err)
309     {
310         auto widgetFrom = RetrieveWidget(imgFrom, err);
311         if (widgetFrom == nullptr || err.code_ != NO_ERROR) {
312             return;
313         }
314         auto widgetTo = RetrieveWidget(imgTo, err, false);
315         if (widgetTo == nullptr || err.code_ != NO_ERROR) {
316             return;
317         }
318         auto boundsFrom = widgetFrom->GetBounds();
319         auto boundsTo = widgetTo->GetBounds();
320         auto centerFrom = Point {boundsFrom.GetCenterX(), boundsFrom.GetCenterY()};
321         auto centerTo = Point {boundsTo.GetCenterX(), boundsTo.GetCenterY()};
322         InjectGenericSwipe(PointerOp::DRAG_P, centerFrom, centerTo, *uiController_, options_);
323     }
324 
FindWidgets(const WidgetSelector & select,vector<unique_ptr<WidgetImage>> & rev,ApiCallErr & err)325     void UiDriver::FindWidgets(const WidgetSelector &select, vector<unique_ptr<WidgetImage>> &rev, ApiCallErr &err)
326     {
327         UpdateUi(true, err);
328         if (err.code_ != NO_ERROR) {
329             return;
330         }
331         vector<reference_wrapper<const Widget>> widgets;
332         select.Select(*widgetTree_, widgets);
333         // covert widgets to images as return value
334         uint32_t index = 0;
335         for (auto &ref:widgets) {
336             auto image = make_unique<WidgetImage>();
337             Widget2Image(ref.get(), *image, select);
338             // at sometime, more than one widgets are found, add the node index to the description
339             auto origDesc = image->GetSelectionDesc();
340             auto newDesc = origDesc + "(index=" + to_string(index) + ")";
341             image->SetSelectionDesc(newDesc);
342             rev.emplace_back(move(image));
343             index++;
344         }
345     }
346 
WaitForWidget(const WidgetSelector & select,uint32_t maxMs,ApiCallErr & err)347     unique_ptr<WidgetImage> UiDriver::WaitForWidget(const WidgetSelector &select, uint32_t maxMs, ApiCallErr &err)
348     {
349         const uint32_t sliceMs = 20;
350         const auto startMs = GetCurrentMillisecond();
351         vector<unique_ptr<WidgetImage>> receiver;
352         do {
353             FindWidgets(select, receiver, err);
354             if (err.code_ != NO_ERROR) { // abort on error
355                 return nullptr;
356             }
357             if (!receiver.empty()) {
358                 return move(receiver.at(0));
359             }
360             DelayMs(sliceMs);
361         } while (GetCurrentMillisecond() - startMs < maxMs);
362         return nullptr;
363     }
364 
UpdateWidgetImage(WidgetImage & image,ApiCallErr & error)365     void UiDriver::UpdateWidgetImage(WidgetImage &image, ApiCallErr &error)
366     {
367         auto widget = RetrieveWidget(image, error);
368         if (widget == nullptr || error.code_ != NO_ERROR) {
369             return;
370         }
371         auto selectionDesc = image.GetSelectionDesc();
372         WidgetSelector selector; // dummy selector
373         Widget2Image(*widget, image, selector);
374         image.SetSelectionDesc(selectionDesc);
375     }
376 
DelayMs(uint32_t ms)377     void UiDriver::DelayMs(uint32_t ms)
378     {
379         if (ms > 0) {
380             this_thread::sleep_for(chrono::milliseconds(ms));
381         }
382     }
383 
PerformGenericClick(PointerOp type,const Point & point,ApiCallErr & err)384     void UiDriver::PerformGenericClick(PointerOp type, const Point &point, ApiCallErr &err)
385     {
386         UpdateUi(false, err);
387         if (err.code_ != NO_ERROR) {
388             return;
389         }
390         InjectGenericClick(type, point, *uiController_, options_);
391     }
392 
PerformGenericSwipe(PointerOp type,const Point & fromPoint,const Point & toPoint,ApiCallErr & err)393     void UiDriver::PerformGenericSwipe(PointerOp type, const Point &fromPoint, const Point &toPoint, ApiCallErr &err)
394     {
395         UpdateUi(false, err);
396         if (err.code_ != NO_ERROR) {
397             return;
398         }
399         InjectGenericSwipe(type, fromPoint, toPoint, *uiController_, options_);
400     }
401 
TakeScreenCap(string_view savePath,ApiCallErr & err)402     void UiDriver::TakeScreenCap(string_view savePath, ApiCallErr &err)
403     {
404         UpdateUi(false, err);
405         if (err.code_ != NO_ERROR) {
406             return;
407         }
408         stringstream errorRecv;
409         if (!uiController_->TakeScreenCap(savePath, errorRecv)) {
410             LOG_W("ScreenCap failed: %{public}s", errorRecv.str().c_str());
411         } else {
412             LOG_D("ScreenCap saved to %{public}s", savePath.data());
413         }
414     }
415 
WriteIntoParcel(json & data) const416     void UiDriver::WriteIntoParcel(json &data) const
417     {
418         data["device_name"] = deviceName_;
419         json options;
420         options_.WriteIntoParcel(options);
421         data["options"] = options;
422     }
423 
ReadFromParcel(const json & data)424     void UiDriver::ReadFromParcel(const json &data)
425     {
426         deviceName_ = data["device_name"];
427         options_.ReadFromParcel(data["options"]);
428     }
429 } // namespace uitest