• 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 <napi/native_api.h>
17 #include <napi/native_node_api.h>
18 #include <string>
19 #include <unistd.h>
20 #include "json.hpp"
21 #include "common_defines.h"
22 #include "common_utilities_hpp.h"
23 
24 namespace OHOS::uitest {
25     using namespace nlohmann;
26     using namespace std;
27 
28     static constexpr size_t NAPI_MAX_STRING_ARG_LENGTH = 1024;
29     static constexpr size_t NAPI_MAX_ARG_COUNT = 8;
30     // type of unexpected or napi-internal error
31     static constexpr napi_status NAPI_ERR = napi_status::napi_generic_failure;
32     // the name of property that represents the By object is global seed.
33     static constexpr char PROP_IS_SEED[] = "isSeedBy_";
34     // the name of property that represents the metadata of the native uitest object
35     static constexpr char PROP_METADATA[] = "nativeObjectMetadata_";
36     // the name of property that represents the DataType id of the js uitest object
37     static constexpr char PROP_TYPE_ID[] = "dataType_";
38     // the name of property that represents the bound UiDriver object of the UiComponent object
39     static constexpr char PROP_BOUND_DRIVER[] = "boundUiDriver_";
40     /**Supported UiComponent attribute types. Ordered by <code>UiAttr</code> definition.*/
41     static constexpr TypeId ATTR_TYPES[11] = {INT, STRING, STRING, STRING, STRING, BOOL, BOOL, BOOL, BOOL, BOOL, BOOL};
42     /**Supported By-relative builder native-API names..Ordered by <code>RelMode</code> definition.*/
43     static constexpr CStr BY_REL_NAMES_CPP[2] = {"WidgetSelector::AddRearLocator", "WidgetSelector::AddFrontLocator"};
44     /**StaticSyncCreator function of 'By', <b>for internal usage only</b> to convert seedBy to new By instance.*/
45     static napi_callback gInternalByCreator = nullptr;
46     /**The transaction implementer function.*/
47     using TransactFuncProto = string (*)(string_view, string_view, string_view);
48 #ifdef __DOUBLE_FRAMEWORK__
49     static TransactFuncProto transactFunc = nullptr;
50     static int32_t count = 0;
GetAndIncreaseCount(napi_env env,napi_callback_info info)51     static napi_value GetAndIncreaseCount(napi_env env, napi_callback_info info)
52     {
53         napi_value val;
54         napi_create_int32(env, count, &val);
55         count++;
56         return val;
57     }
58 
SetTransactFunc(napi_env env,napi_callback_info info)59     static napi_value SetTransactFunc(napi_env env, napi_callback_info info)
60     {
61         size_t argc = 1;
62         napi_value jsThis = nullptr;
63         napi_value argv[1] = {0};
64         NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr));
65         NAPI_ASSERT(env, argc == 1, "need 1 parameter");
66 
67         napi_valuetype valueType = napi_undefined;
68         napi_typeof(env, argv[0], &valueType);
69         NAPI_ASSERT(env, valueType == napi_number, "unexpected non-number parameter");
70 #if defined(__LP64__)
71         napi_get_value_int64(env, argv[0], reinterpret_cast <int64_t *>(&transactFunc));
72 #else
73         napi_get_value_int32(env, argv[0], reinterpret_cast <int32_t*>(&transactFunc));
74 #endif
75         return nullptr;
76     }
77 
78 #else
79 
80     // use external setup/transact/disposal callback functions
81     extern bool SetupTransactionEnv(string_view token);
82 
83     extern string TransactionClientFunc(string_view apiId, string_view caller, string_view params);
84 
85     extern void DisposeTransactionEnv();
86 
87     static TransactFuncProto transactFunc = TransactionClientFunc;
88 
89     /**Lifecycle callback, setup transaction environment, called externally.*/
EnvironmentSetup(napi_env env,napi_callback_info info)90     static napi_value EnvironmentSetup(napi_env env, napi_callback_info info)
91     {
92         static auto setUpDone = false;
93         if (setUpDone) {
94             return nullptr;
95         }
96         LOG_I("Begin setup transaction environment");
97         size_t argc = 1;
98         napi_value value = nullptr;
99         napi_value argv[1] = {0};
100         NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &value, nullptr));
101         NAPI_ASSERT(env, argc > 0, "Need session token argument!");
102         constexpr size_t bufSize = NAPI_MAX_STRING_ARG_LENGTH;
103         char buf[bufSize] = {0};
104         size_t strLen = 0;
105         NAPI_CALL(env, napi_get_value_string_utf8(env, argv[0], buf, bufSize, &strLen));
106         setUpDone = SetupTransactionEnv(string_view(buf, strLen));
107         LOG_I("End setup transaction environment, result=%{public}d", setUpDone);
108         NAPI_ASSERT(env, setUpDone, "SetupTransactionEnv failed");
109         return nullptr;
110     }
111 
112     /**Lifecycle callback, teardown transaction environment, called externally.*/
EnvironmentTeardown(napi_env env,napi_callback_info info)113     static napi_value EnvironmentTeardown(napi_env env, napi_callback_info info)
114     {
115         LOG_I("Begin teardown transaction environment");
116         DisposeTransactionEnv();
117         LOG_I("End teardown transaction environment");
118         return nullptr;
119     }
120 
121 #endif
122 
123     /**Encapsulates the data objects needed in once api transaction.*/
124     struct TransactionData {
125         string_view apiId_;
126         bool isStaticApi_ = false;
127         size_t argc_ = NAPI_MAX_ARG_COUNT;
128         napi_value jsThis_ = nullptr;
129         napi_value argv_[NAPI_MAX_ARG_COUNT] = {nullptr};
130         TypeId argTypes_[NAPI_MAX_ARG_COUNT] = {TypeId::NONE};
131         TypeId returnType_ = TypeId::NONE;
132         // custom inspector that evaluates the transaction result value and returns napi_error is any.
133         function<napi_value(napi_env, napi_value)> resultInspector_ = nullptr;
134         // the parcel data fields
135         string jsThisParcel_;
136         string argvParcel_;
137     };
138 
CreateJsException(napi_env env,string_view code,string_view msg)139     static napi_value CreateJsException(napi_env env, string_view code, string_view msg)
140     {
141         napi_value codeValue, msgValue, errorValue;
142         napi_create_string_utf8(env, code.data(), NAPI_AUTO_LENGTH, &codeValue);
143         napi_create_string_utf8(env, msg.data(), NAPI_AUTO_LENGTH, &msgValue);
144         napi_create_error(env, codeValue, msgValue, &errorValue);
145         return errorValue;
146     }
147 
148     /**Set or update object property of string type.*/
SetOrUpdateStrProp(napi_env env,napi_value obj,string_view name,string_view value)149     inline static napi_status SetOrUpdateStrProp(napi_env env, napi_value obj, string_view name, string_view value)
150     {
151         napi_value nv = nullptr;
152         NAPI_CALL_BASE(env, napi_create_string_utf8(env, value.data(), value.length(), &nv), NAPI_ERR);
153         return napi_set_named_property(env, obj, name.data(), nv);
154     }
155 
156     /**Set object constructor function to global as attribute.*/
MountJsConstructorToGlobal(napi_env env,TypeId id,napi_value function)157     static napi_status MountJsConstructorToGlobal(napi_env env, TypeId id, napi_value function)
158     {
159         NAPI_ASSERT_BASE(env, function != nullptr, "Null constructor function", napi_invalid_arg);
160         string name = "constructor_" + to_string(id);
161         napi_value global = nullptr;
162         NAPI_CALL_BASE(env, napi_get_global(env, &global), NAPI_ERR);
163         NAPI_CALL_BASE(env, napi_set_named_property(env, global, name.c_str(), function), NAPI_ERR);
164         return napi_ok;
165     }
166 
167     /**Get object constructor function from global as attribute.*/
GetJsConstructorFromGlobal(napi_env env,TypeId id,napi_value * pFunction)168     static napi_status GetJsConstructorFromGlobal(napi_env env, TypeId id, napi_value* pFunction)
169     {
170         NAPI_ASSERT_BASE(env, pFunction != nullptr, "Null constructor receiver", napi_invalid_arg);
171         string name = "constructor_" + to_string(id);
172         napi_value global = nullptr;
173         NAPI_CALL_BASE(env, napi_get_global(env, &global), NAPI_ERR);
174         NAPI_CALL_BASE(env, napi_get_named_property(env, global, name.c_str(), pFunction), NAPI_ERR);
175         return napi_ok;
176     }
177 
178     /**Marshal object into json, throw error and return false if the object cannot be serialized.*/
MarshalObject(napi_env env,napi_value in,TypeId type,json & out)179     static napi_status MarshalObject(napi_env env, napi_value in, TypeId type, json &out)
180     {
181         NAPI_ASSERT_BASE(env, in != nullptr, "Illegal null arguments", napi_invalid_arg);
182         size_t len = 0;
183         bool bValue = false;
184         int32_t iValue = 0;
185         double fValue = 0;
186         constexpr size_t bufSize = NAPI_MAX_STRING_ARG_LENGTH;
187         char sValue[bufSize] = {0};
188         napi_value oValue = nullptr;
189         out[KEY_DATA_TYPE] = type;
190         switch (type) {
191             case TypeId::BOOL:
192                 NAPI_CALL_BASE(env, napi_get_value_bool(env, in, &bValue), NAPI_ERR);
193                 out[KEY_DATA_VALUE] = bValue;
194                 break;
195             case TypeId::INT:
196                 NAPI_CALL_BASE(env, napi_get_value_int32(env, in, &iValue), NAPI_ERR);
197                 out[KEY_DATA_VALUE] = iValue;
198                 break;
199             case TypeId::FLOAT:
200                 NAPI_CALL_BASE(env, napi_get_value_double(env, in, &fValue), NAPI_ERR);
201                 out[KEY_DATA_VALUE] = fValue;
202                 break;
203             case TypeId::STRING:
204                 NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, in, sValue, bufSize, &len), NAPI_ERR);
205                 out[KEY_DATA_VALUE] = string(sValue);
206                 break;
207             default:
208                 NAPI_CALL_BASE(env, napi_get_named_property(env, in, PROP_METADATA, &oValue), NAPI_ERR);
209                 NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, oValue, sValue, bufSize, &len), NAPI_ERR);
210                 out[KEY_DATA_VALUE] = json::parse(string(sValue)); // marshal as json-object
211                 break;
212         }
213         return napi_ok;
214     }
215 
216     /**Unmarshal object from json, throw error and return false if the object cannot be deserialized.*/
UnmarshalObject(napi_env env,const json & in,napi_value * pOut,const TransactionData & tp)217     static napi_status UnmarshalObject(napi_env env, const json &in, napi_value *pOut, const TransactionData &tp)
218     {
219         NAPI_ASSERT_BASE(env, pOut != nullptr && in.contains(KEY_DATA_VALUE), "Illegal arguments", napi_invalid_arg);
220         bool bVal;
221         int32_t iValue;
222         double dVal;
223         string sVal;
224         uint32_t typeId = in[KEY_DATA_TYPE];
225         NAPI_ASSERT_BASE(env, typeId == tp.returnType_, "Illegal result value type", napi_invalid_arg);
226         switch (tp.returnType_) {
227             case TypeId::BOOL:
228                 bVal = in[KEY_DATA_VALUE];
229                 NAPI_CALL_BASE(env, napi_get_boolean(env, bVal, pOut), NAPI_ERR);
230                 break;
231             case TypeId::INT:
232                 iValue = in[KEY_DATA_VALUE];
233                 NAPI_CALL_BASE(env, napi_create_int32(env, iValue, pOut), NAPI_ERR);
234                 break;
235             case TypeId::FLOAT:
236                 dVal = in[KEY_DATA_VALUE];
237                 NAPI_CALL_BASE(env, napi_create_double(env, dVal, pOut), NAPI_ERR);
238                 break;
239             case TypeId::STRING:
240                 sVal = in[KEY_DATA_VALUE];
241                 NAPI_CALL_BASE(env, napi_create_string_utf8(env, sVal.c_str(), sVal.length(), pOut), NAPI_ERR);
242                 break;
243             default:
244                 // create object instance and bind metadata
245                 napi_value constructor;
246                 NAPI_CALL_BASE(env, GetJsConstructorFromGlobal(env, tp.returnType_, &constructor), NAPI_ERR);
247                 NAPI_CALL_BASE(env, napi_new_instance(env, constructor, 0, nullptr, pOut), NAPI_ERR);
248                 sVal = in[KEY_DATA_VALUE].dump();
249                 NAPI_CALL_BASE(env, SetOrUpdateStrProp(env, *pOut, PROP_METADATA, sVal), NAPI_ERR);
250                 if (tp.returnType_ == COMPONENT) {
251                     // bind the the UiDriver that find this UiComponent
252                     NAPI_CALL_BASE(env, napi_set_named_property(env, *pOut, PROP_BOUND_DRIVER, tp.jsThis_), NAPI_ERR);
253                 }
254                 break;
255         }
256         return napi_ok;
257     }
258 
259     /**Generate transaction outgoing arguments-data parcel.*/
MarshalTransactionData(napi_env env,TransactionData & tp)260     static napi_status MarshalTransactionData(napi_env env, TransactionData &tp)
261     {
262         LOG_D("Start to marshal transaction parameters, count=%{public}d", tp.argc_);
263         auto paramList = json::array();
264         for (size_t idx = 0; idx < tp.argc_; idx++) {
265             json paramItem;
266             NAPI_CALL_BASE(env, MarshalObject(env, tp.argv_[idx], tp.argTypes_[idx], paramItem), NAPI_ERR);
267             paramList.emplace_back(paramItem);
268             // erase parameters which are expected not to be used in the reset transaction procedure
269             tp.argv_[idx] = nullptr;
270         }
271         tp.argc_ = 0;
272         tp.argvParcel_ = paramList.dump();
273         if (!tp.isStaticApi_ && tp.jsThis_ != nullptr) {
274             json receiverItem;
275             LOG_D("Start to serialize jsThis");
276             napi_value typeProp = nullptr;
277             NAPI_CALL_BASE(env, napi_get_named_property(env, tp.jsThis_, PROP_TYPE_ID, &typeProp), NAPI_ERR);
278             uint32_t id = TypeId::NONE;
279             NAPI_CALL_BASE(env, napi_get_value_uint32(env, typeProp, &id), NAPI_ERR);
280             NAPI_CALL_BASE(env, MarshalObject(env, tp.jsThis_, static_cast<TypeId>(id), receiverItem), NAPI_ERR);
281             tp.jsThisParcel_ = receiverItem[KEY_DATA_VALUE].dump();
282         } else {
283             tp.jsThisParcel_ = "{}";
284         }
285         return napi_ok;
286     }
287 
288     /**Evaluate and convert transaction result-data parcel to object. Return the exception raised during the
289      * transaction if any, else return the result object. */
290     template<bool kReturnMultiple = false>
UnmarshalTransactResult(napi_env env,TransactionData & tp,string_view resultParcel)291     static napi_value UnmarshalTransactResult(napi_env env, TransactionData &tp, string_view resultParcel)
292     {
293         napi_value result = nullptr;
294         NAPI_CALL(env, napi_get_undefined(env, &result));
295         LOG_D("Start to Unmarshal transaction results: '%{public}s'", resultParcel.data());
296         json reply = json::parse(resultParcel);
297         NAPI_ASSERT(env, reply.contains(KEY_UPDATED_CALLER) && reply.contains(KEY_RESULT_VALUES), "Fields missing");
298         if (reply.contains(KEY_EXCEPTION)) {
299             json error = reply[KEY_EXCEPTION];
300             string codeStr = error.contains(KEY_CODE) ? error[KEY_CODE] : "unknown";
301             string msgStr = error.contains(KEY_MESSAGE) ? error[KEY_MESSAGE] : "unknown";
302             return CreateJsException(env, codeStr, msgStr);
303         }
304         if (!tp.isStaticApi_ && tp.jsThis_ != nullptr) {
305             LOG_D("Begin to update jsThis");
306             const string meta = reply[KEY_UPDATED_CALLER].dump();
307             NAPI_CALL(env, SetOrUpdateStrProp(env, tp.jsThis_, PROP_METADATA, meta));
308         }
309         if (tp.returnType_ == TypeId::NONE) {
310             return result;
311         }
312         json resultValues = reply[KEY_RESULT_VALUES];
313         const size_t resultCount = resultValues.size();
314         napi_value objects;
315         NAPI_CALL(env, napi_create_array_with_length(env, resultCount, &objects));
316         LOG_D("Begin to deserialize result, count=%{public}zu", resultCount);
317         for (size_t idx = 0; idx < resultCount; idx++) {
318             napi_value obj = nullptr;
319             NAPI_CALL(env, UnmarshalObject(env, resultValues.at(idx), &obj, tp));
320             NAPI_CALL(env, napi_set_element(env, objects, idx, obj));
321         }
322         if constexpr(kReturnMultiple) {
323             result = objects;
324         } else if (resultCount > 0) {
325             NAPI_CALL(env, napi_get_element(env, objects, 0, &result));
326         }
327         return tp.resultInspector_ != nullptr ? tp.resultInspector_(env, result) : result;
328     }
329 
330     /**Call api with parameters out, wait for and return result value or throw raised exception.*/
331     template<bool kReturnMultiple = false>
TransactSync(napi_env env,TransactionData & tp)332     static napi_value TransactSync(napi_env env, TransactionData &tp)
333     {
334         LOG_D("TargetApi=%{public}s", tp.apiId_.data());
335         NAPI_CALL(env, MarshalTransactionData(env, tp));
336         auto resultParcel = transactFunc(tp.apiId_.data(), tp.jsThisParcel_.c_str(), tp.argvParcel_.c_str());
337         auto resultValue = UnmarshalTransactResult<kReturnMultiple>(env, tp, resultParcel);
338         auto isError = false;
339         NAPI_CALL(env, napi_is_error(env, resultValue, &isError));
340         if (isError) {
341             NAPI_CALL(env, napi_throw(env, resultValue));
342             NAPI_CALL(env, napi_get_undefined(env, &resultValue)); // return undefined it's error
343         }
344         return resultValue;
345     }
346 
347     /**Encapsulates the data objects needed for async transaction.*/
348     struct AsyncTransactionData {
349         TransactionData data_;
350         string resultParcel_;
351         napi_async_work asyncWork_ = nullptr;
352         napi_deferred deferred_ = nullptr;
353         napi_ref jsThisRef_ = nullptr;
354     };
355 
356     /**Call api with parameters out, return a promise.*/
357     template<bool kReturnMultiple = false>
TransactAsync(napi_env env,TransactionData & tp)358     static napi_value TransactAsync(napi_env env, TransactionData &tp)
359     {
360         static constexpr uint32_t refCount = 1;
361         LOG_D("TargetApi=%{public}s", tp.apiId_.data());
362         NAPI_CALL(env, MarshalTransactionData(env, tp));
363         napi_value resName;
364         NAPI_CALL(env, napi_create_string_latin1(env, __FUNCTION__, NAPI_AUTO_LENGTH, &resName));
365         auto atd = new AsyncTransactionData();
366         atd->data_ = tp;
367         napi_value promise;
368         NAPI_CALL(env, napi_create_promise(env, &(atd->deferred_), &promise));
369         NAPI_CALL(env, napi_create_reference(env, tp.jsThis_, refCount, &(atd->jsThisRef_)));
370         napi_create_async_work(env, nullptr, resName,
371             [](napi_env env, void *data) {
372                 auto atd = reinterpret_cast<AsyncTransactionData *>(data);
373                 // NOT:: use 'auto&' rather than 'auto', or the result will be set to copy-constructed temp-object
374                 auto &tp = atd->data_;
375                 atd->resultParcel_ = transactFunc(tp.apiId_.data(), tp.jsThisParcel_.c_str(), tp.argvParcel_.c_str());
376             },
377             [](napi_env env, napi_status status, void *data) {
378                 auto atd = reinterpret_cast<AsyncTransactionData *>(data);
379                 napi_get_reference_value(env, atd->jsThisRef_, &(atd->data_.jsThis_));
380                 auto resultValue = UnmarshalTransactResult<kReturnMultiple>(env, atd->data_, atd->resultParcel_);
381                 napi_delete_reference(env, atd->jsThisRef_);
382                 auto isError = false;
383                 napi_is_error(env, resultValue, &isError);
384                 if (isError) {
385                     napi_reject_deferred(env, atd->deferred_, resultValue);
386                 } else {
387                     napi_resolve_deferred(env, atd->deferred_, resultValue);
388                 }
389                 delete atd;
390             },
391             (void *) atd, &(atd->asyncWork_));
392         napi_queue_async_work(env, atd->asyncWork_);
393         return promise;
394     }
395 
396     /**Extract transaction data from callback_info arguments and check the data types.*/
ExtractTransactionData(napi_env env,napi_callback_info info,size_t minArgc,const vector<TypeId> & argTypes,TransactionData & tp)397     static napi_status ExtractTransactionData(napi_env env, napi_callback_info info, size_t minArgc,
398                                               const vector<TypeId> &argTypes, TransactionData &tp)
399     {
400         tp.argc_ = NAPI_MAX_ARG_COUNT; // extract as much argument as possible
401         NAPI_CALL_BASE(env, napi_get_cb_info(env, info, &(tp.argc_), tp.argv_, &(tp.jsThis_), nullptr), NAPI_ERR);
402         if (!tp.isStaticApi_) {
403             NAPI_ASSERT_BASE(env, tp.jsThis_ != nullptr, "Null jsThis!", napi_invalid_arg);
404         } else {
405             NAPI_CALL_BASE(env, napi_get_undefined(env, &(tp.jsThis_)), NAPI_ERR);
406         }
407         // check argument count and types
408         NAPI_ASSERT_BASE(env, tp.argc_ >= minArgc, "Illegal argument count", napi_invalid_arg);
409         NAPI_ASSERT_BASE(env, argTypes.size() >= tp.argc_, "Illegal argument count", napi_invalid_arg);
410         napi_valuetype valueType;
411         for (size_t idx = 0; idx < tp.argc_; idx++) {
412             tp.argTypes_[idx] = argTypes.at(idx);
413             NAPI_CALL_BASE(env, napi_typeof(env, tp.argv_[idx], &valueType), NAPI_ERR);
414             const bool isNullOrUndefined = valueType == napi_null || valueType == napi_undefined;
415             NAPI_ASSERT_BASE(env, !isNullOrUndefined, "Null argument", napi_invalid_arg);
416             const TypeId dt = argTypes.at(idx);
417             if (dt == INT || dt == FLOAT) {
418                 NAPI_ASSERT_BASE(env, valueType == napi_number, "Illegal argument type", napi_number_expected);
419             } else if (dt == BOOL) {
420                 NAPI_ASSERT_BASE(env, valueType == napi_boolean, "Illegal argument type", napi_boolean_expected);
421             } else if (dt == STRING) {
422                 NAPI_ASSERT_BASE(env, valueType == napi_string, "Illegal argument type", napi_string_expected);
423             } else {
424                 NAPI_ASSERT_BASE(env, valueType == napi_object, "Illegal argument type", napi_object_expected);
425                 // check the typeId property (set during object initialization)
426                 napi_value idProp = nullptr;
427                 int32_t idValue = TypeId::NONE;
428                 NAPI_CALL_BASE(env, napi_get_named_property(env, tp.argv_[idx], PROP_TYPE_ID, &idProp), NAPI_ERR);
429                 NAPI_CALL_BASE(env, napi_get_value_int32(env, idProp, &idValue), NAPI_ERR);
430                 NAPI_ASSERT_BASE(env, idValue == dt, "Illegal argument type", napi_invalid_arg);
431             }
432         }
433         return napi_ok;
434     }
435 
436     /**Generic template of functions that run asynchronously.*/
437     template<CStr kNativeApiId, TypeId kReturnType, bool kReturnMultiple, TypeId... kArgTypes>
GenericAsyncFunc(napi_env env,napi_callback_info info)438     static napi_value GenericAsyncFunc(napi_env env, napi_callback_info info)
439     {
440         static_assert(!string_view(kNativeApiId).empty(), "Native function name cannot be empty");
441         static constexpr size_t argc = sizeof...(kArgTypes);
442         vector<TypeId> types = {};
443         if constexpr(argc > 0) {
444             types = {kArgTypes...};
445         }
446         TransactionData tp {.apiId_ = kNativeApiId, .returnType_ = kReturnType};
447         NAPI_CALL(env, ExtractTransactionData(env, info, argc, types, tp));
448         return TransactAsync<kReturnMultiple>(env, tp);
449     }
450 
451     /**Template of JS wrapper-object static creator functions.*/
452     template<CStr kNativeApiId, TypeId kReturnType, TypeId... kArgTypes>
StaticSyncCreator(napi_env env,napi_callback_info info)453     static napi_value StaticSyncCreator(napi_env env, napi_callback_info info)
454     {
455         static_assert(!string_view(kNativeApiId).empty(), "Native function name cannot be empty");
456         static constexpr size_t argc = sizeof...(kArgTypes);
457         vector<TypeId> types = {};
458         if constexpr(argc > 0) {
459             types = {kArgTypes...};
460         }
461         TransactionData tp = {.apiId_=kNativeApiId, .isStaticApi_ = true, .returnType_=kReturnType};
462         if (info == nullptr) {
463             // allow call without arguments
464             tp.argc_ = 0;
465             tp.jsThis_ = nullptr;
466         } else {
467             NAPI_CALL(env, ExtractTransactionData(env, info, argc, types, tp));
468         }
469         return TransactSync(env, tp);
470     }
471 
472     /**Convert global seed-By (for program syntactic sugar purpose) to new By object.*/
EnsureNonSeedBy(napi_env env,TransactionData & tp)473     static napi_status EnsureNonSeedBy(napi_env env, TransactionData &tp)
474     {
475         bool hasProp = false;
476         NAPI_CALL_BASE(env, napi_has_named_property(env, tp.jsThis_, PROP_IS_SEED, &hasProp), NAPI_ERR);
477         if (hasProp) {
478             NAPI_ASSERT_BASE(env, gInternalByCreator != nullptr, "Static By-Creator is null", NAPI_ERR);
479             LOG_D("Convert seedBy to new instance");
480             tp.jsThis_ = gInternalByCreator(env, nullptr);
481         }
482         return napi_ok;
483     }
484 
485     /**Conversion between value and string, using the builtin JSON methods.*/
ValueStringConvert(napi_env env,napi_value in,napi_value * out,TypeId inType,TypeId outType)486     static napi_status ValueStringConvert(napi_env env, napi_value in, napi_value *out, TypeId inType, TypeId outType)
487     {
488         if (in == nullptr || out == nullptr) {
489             return napi_invalid_arg;
490         }
491         if (inType == outType) {
492             *out = in;
493             return napi_ok;
494         }
495         napi_value global = nullptr;
496         napi_value jsonProp = nullptr;
497         napi_value jsonFunc = nullptr;
498         NAPI_CALL_BASE(env, napi_get_global(env, &global), NAPI_ERR);
499         NAPI_CALL_BASE(env, napi_get_named_property(env, global, "JSON", &jsonProp), NAPI_ERR);
500         if (outType == TypeId::STRING) {
501             NAPI_CALL_BASE(env, napi_get_named_property(env, jsonProp, "stringify", &jsonFunc), NAPI_ERR);
502         } else {
503             NAPI_CALL_BASE(env, napi_get_named_property(env, jsonProp, "parse", &jsonFunc), NAPI_ERR);
504         }
505         napi_value argv[1] = {in};
506         NAPI_CALL_BASE(env, napi_call_function(env, jsonProp, jsonFunc, 1, argv, out), NAPI_ERR);
507         return napi_ok;
508     }
509 
510     /**Template for plain attribute By-builder functions.*/
511     template<UiAttr kAttr>
ByAttrBuilder(napi_env env,napi_callback_info info)512     static napi_value ByAttrBuilder(napi_env env, napi_callback_info info)
513     {
514         static constexpr auto attrName = ATTR_NAMES[kAttr];
515         static constexpr auto attrType = ATTR_TYPES[kAttr];
516         // incoming args: testValue, matchPattern(optional)
517         TransactionData tp = {.apiId_= "WidgetSelector::AddMatcher"};
518         NAPI_CALL(env, ExtractTransactionData(env, info, 0, {attrType, TypeId::INT}, tp));
519         if (attrType == TypeId::BOOL && tp.argc_ == 0) {
520             // for attribute of type bool, the input-arg is default to true: By.enabled()===By.enabled(true)
521             NAPI_CALL(env, napi_get_boolean(env, true, &(tp.argv_[INDEX_ZERO])));
522             tp.argTypes_[INDEX_ZERO] = TypeId::BOOL;
523             tp.argc_++;
524         }
525         NAPI_ASSERT(env, tp.argc_ >= 1, "Insufficient argument"); // require attribute testValue
526         NAPI_CALL(env, EnsureNonSeedBy(env, tp));
527         if (tp.argc_ == 1) {
528             // fill-in default match pattern
529             NAPI_CALL(env, napi_create_int32(env, ValueMatchRule::EQ, &(tp.argv_[INDEX_TWO])));
530             tp.argTypes_[INDEX_TWO] = TypeId::INT;
531             tp.argc_++;
532         } else {
533             // move match pattern to index2
534             tp.argv_[INDEX_TWO] = tp.argv_[INDEX_ONE];
535             tp.argTypes_[INDEX_TWO] = TypeId::INT;
536         }
537         // move testValue to index1, [convert it from any type to string]
538         NAPI_CALL(env, ValueStringConvert(env, tp.argv_[INDEX_ZERO], &(tp.argv_[INDEX_ONE]), attrType, TypeId::STRING));
539         tp.argTypes_[INDEX_ONE] = TypeId::STRING;
540         // fill-in attribute name to index0
541         NAPI_CALL(env, napi_create_string_utf8(env, attrName, NAPI_AUTO_LENGTH, &(tp.argv_[INDEX_ZERO])));
542         tp.argTypes_[INDEX_ZERO] = TypeId::STRING;
543         tp.argc_++;
544         TransactSync(env, tp);
545         // return jsThis, which has updated its metaData in the transaction
546         return tp.jsThis_;
547     }
548 
549     /**Template for relative By-builder functions.*/
550     template<RelMode kRelMode>
ByRelBuilder(napi_env env,napi_callback_info info)551     static napi_value ByRelBuilder(napi_env env, napi_callback_info info)
552     {
553         static constexpr auto apiId = BY_REL_NAMES_CPP[kRelMode];
554         // incoming args: relative-By
555         TransactionData tp = {.apiId_=apiId};
556         NAPI_CALL(env, ExtractTransactionData(env, info, 1, {TypeId::BY}, tp));
557         NAPI_CALL(env, EnsureNonSeedBy(env, tp));
558         TransactSync(env, tp);
559         // return jsThis, which has updated its metaData in the transaction
560         return tp.jsThis_;
561     }
562 
563     /**Template for all UiComponent-attribute-getter functions, which forward invocation to bound UiDriver api.*/
564     template<UiAttr kAttr>
ComponentAttrGetter(napi_env env,napi_callback_info info)565     static napi_value ComponentAttrGetter(napi_env env, napi_callback_info info)
566     {
567         static constexpr auto attrName = ATTR_NAMES[kAttr];
568         static constexpr auto attrType = ATTR_TYPES[kAttr];
569         // retrieve attribute value as string and convert to target type
570         TransactionData tp = {.apiId_= "UiDriver::GetWidgetAttribute", .returnType_=TypeId::STRING};
571         tp.resultInspector_ = [](napi_env env, napi_value result) -> napi_value {
572             napi_valuetype type = napi_null;
573             if (result != nullptr) {
574                 NAPI_CALL(env, napi_typeof(env, result, &type));
575             }
576             if (type == napi_null || type == napi_undefined) {
577                 return result;
578             } else {
579                 napi_value convertedResult = result;
580                 NAPI_CALL(env, ValueStringConvert(env, result, &convertedResult, TypeId::STRING, attrType));
581                 return convertedResult;
582             }
583         };
584         NAPI_CALL(env, ExtractTransactionData(env, info, 0, {}, tp));
585         // get the holding uiDriver which has found and will operate me
586         napi_value uiDriver = nullptr;
587         NAPI_CALL(env, napi_get_named_property(env, tp.jsThis_, PROP_BOUND_DRIVER, &uiDriver));
588         NAPI_ASSERT(env, uiDriver != nullptr, "UiDriver not found for UiComponent");
589         // Transformation:: {widget.getId() ===> uiDriver.GetWidgetAttribute(widget,"id")}
590         tp.argv_[0] = tp.jsThis_;
591         tp.argTypes_[0] = TypeId::COMPONENT;
592         tp.jsThis_ = uiDriver;
593         tp.argc_++;
594         // add attributeName parameter
595         NAPI_CALL(env, napi_create_string_utf8(env, attrName, NAPI_AUTO_LENGTH, &(tp.argv_[1])));
596         tp.argTypes_[1] = TypeId::STRING;
597         tp.argc_++;
598         return TransactAsync(env, tp);
599     }
600 
601     /**Template for all UiComponent touch functions, which forward invocation to bound UiDriver api, return void.*/
602     template<WidgetOp kAction>
ComponentToucher(napi_env env,napi_callback_info info)603     static napi_value ComponentToucher(napi_env env, napi_callback_info info)
604     {
605         TransactionData tp = {.apiId_= "UiDriver::PerformWidgetOperate", .returnType_=TypeId::NONE};
606         NAPI_CALL(env, ExtractTransactionData(env, info, 0, {}, tp));
607         // get the holding uiDriver which has found and will operate me
608         napi_value uiDriver = nullptr;
609         NAPI_CALL(env, napi_get_named_property(env, tp.jsThis_, PROP_BOUND_DRIVER, &uiDriver));
610         NAPI_ASSERT(env, uiDriver != nullptr, "UiDriver not found for UiComponent");
611         // Transformation:: {widget.click() ===> uiDriver.PerformAction(widget,CLICK)}
612         tp.argv_[0] = tp.jsThis_;
613         tp.argTypes_[0] = TypeId::COMPONENT;
614         tp.jsThis_ = uiDriver;
615         tp.argc_++;
616         // add actionCode parameter
617         NAPI_CALL(env, napi_create_int32(env, kAction, &(tp.argv_[1])));
618         tp.argTypes_[1] = TypeId::INT;
619         tp.argc_++;
620         return TransactAsync(env, tp);
621     }
622 
623     /**Generic template for all UiComponent functions, which forward invocation to bound UiDriver api.*/
624     template<CStr kNativeApiId, TypeId kReturnType, TypeId... kArgTypes>
GenericComponentFunc(napi_env env,napi_callback_info info)625     static napi_value GenericComponentFunc(napi_env env, napi_callback_info info)
626     {
627         static_assert(!string_view(kNativeApiId).empty(), "Native function name cannot be empty");
628         static constexpr size_t argc = sizeof...(kArgTypes);
629         vector<TypeId> types = {};
630         if constexpr(argc > 0) {
631             types = {kArgTypes...};
632         }
633         TransactionData tp = {.apiId_= kNativeApiId, .returnType_=kReturnType};
634         NAPI_CALL(env, ExtractTransactionData(env, info, argc, types, tp));
635         // get the holding uiDriver which has found and will operate me
636         napi_value uiDriver = nullptr;
637         NAPI_CALL(env, napi_get_named_property(env, tp.jsThis_, PROP_BOUND_DRIVER, &uiDriver));
638         NAPI_ASSERT(env, uiDriver != nullptr, "UiDriver not found for UiComponent");
639         // Transformation:: {widget.func(arg0,arg1) ===> uiDriver.func(widget,arg0,arg1)}, need right shift args
640         for (size_t idx = tp.argc_; idx > 0; idx--) {
641             tp.argv_[idx] = tp.argv_[idx - 1];
642             tp.argTypes_[idx] = tp.argTypes_[idx - 1];
643         }
644         tp.argv_[0] = tp.jsThis_;
645         tp.argTypes_[0] = TypeId::COMPONENT;
646         tp.jsThis_ = uiDriver;
647         tp.argc_++;
648         return TransactAsync(env, tp);
649     }
650 
651     /**Template for all UiDriver-key-action functions, return void.*/
652     template<UiKey kKey>
UiDriverKeyOperator(napi_env env,napi_callback_info info)653     static napi_value UiDriverKeyOperator(napi_env env, napi_callback_info info)
654     {
655         TransactionData tp = {.apiId_= "UiDriver::TriggerKey"};
656         NAPI_CALL(env, ExtractTransactionData(env, info, 0, {TypeId::INT}, tp));
657         if constexpr(kKey != UiKey::GENERIC) {
658             // for named key, add keyCode parameter
659             tp.argc_ = 1;
660             NAPI_CALL(env, napi_create_int32(env, kKey, &(tp.argv_[0])));
661             tp.argTypes_[0] = TypeId::INT;
662         } else {
663             // for generic key, require keyCode from argv
664             NAPI_ASSERT(env, tp.argc_ >= 1, "Need keyCode argument");
665             for (size_t idx = tp.argc_; idx > 0; idx--) {
666                 tp.argv_[idx] = tp.argv_[idx - 1];
667                 tp.argTypes_[idx] = tp.argTypes_[idx - 1];
668             }
669             NAPI_CALL(env, napi_create_int32(env, kKey, &(tp.argv_[0])));
670             tp.argTypes_[0] = TypeId::INT;
671             tp.argc_++;
672         }
673         return TransactAsync(env, tp);
674     }
675 
676     /**Find and assert component matching given selector exist. <b>(async function)</b>*/
UiDriverComponentExistAsserter(napi_env env,napi_callback_info info)677     static napi_value UiDriverComponentExistAsserter(napi_env env, napi_callback_info info)
678     {
679         TransactionData tp = {.apiId_= "UiDriver::FindWidgets", .returnType_=TypeId::COMPONENT};
680         NAPI_CALL(env, ExtractTransactionData(env, info, 1, {TypeId::BY}, tp));
681         tp.resultInspector_ = [](napi_env env, napi_value result) -> napi_value {
682             napi_valuetype type = napi_null;
683             if (result != nullptr) {
684                 NAPI_CALL(env, napi_typeof(env, result, &type));
685             }
686             if (type == napi_null || type == napi_undefined) {
687                 return CreateJsException(env, "ComponentExistAssertionFailure", "ComponentNotExist");
688             } else {
689                 return result;
690             }
691         };
692         return TransactAsync(env, tp);
693     }
694 
695     /**Template for all UiDriver single-pointer-based touch functions (generic-click/swipe/drag), return void.*/
696     template<PointerOp kAction>
SinglePointToucher(napi_env env,napi_callback_info info)697     static napi_value SinglePointToucher(napi_env env, napi_callback_info info)
698     {
699         static constexpr auto isGenericSwipe = kAction >= PointerOp::SWIPE_P && kAction <= PointerOp::DRAG_P;
700         constexpr size_t argC = isGenericSwipe ? 4 : 2;
701         TransactionData tp {};
702         NAPI_CALL(env, ExtractTransactionData(env, info, argC,
703             {TypeId::INT, TypeId::INT, TypeId::INT, TypeId::INT}, tp));
704         if constexpr (isGenericSwipe) {
705             tp.apiId_ = "UiDriver::PerformGenericSwipe";
706         } else {
707             tp.apiId_ = "UiDriver::PerformGenericClick";
708         }
709         // add action type as 1st parameter (right-shift provided argv, do from right to left to avoid overwriting)
710         for (size_t idx = tp.argc_; idx > 0; idx--) {
711             tp.argv_[idx] = tp.argv_[idx - 1];
712             tp.argTypes_[idx] = tp.argTypes_[idx - 1];
713         }
714         NAPI_CALL(env, napi_create_uint32(env, kAction, &(tp.argv_[0])));
715         tp.argc_++;
716         tp.argTypes_[tp.argc_] = TypeId::INT;
717         return TransactAsync(env, tp);
718     }
719 
720     /**Template of function that initialize <b>unbound</b> uitest js wrapper-objects.*/
721     template<TypeId kObjectType>
JsObjectInitializer(napi_env env,napi_callback_info info)722     static napi_value JsObjectInitializer(napi_env env, napi_callback_info info)
723     {
724         TransactionData tp;
725         NAPI_CALL(env, ExtractTransactionData(env, info, 0, {}, tp));
726         // set DataType property to jsThis
727         napi_value typeId = nullptr;
728         NAPI_CALL(env, napi_create_int32(env, kObjectType, &typeId));
729         NAPI_CALL(env, napi_set_named_property(env, tp.jsThis_, PROP_TYPE_ID, typeId));
730         return tp.jsThis_;
731     }
732 
733     /**Exports uitest js wrapper-classes and its global constructor.*/
ExportClass(napi_env env,napi_value exports,string_view name,TypeId type,napi_callback initializer,const napi_property_descriptor * methods,size_t num)734     static napi_value ExportClass(napi_env env, napi_value exports, string_view name, TypeId type,
735                                   napi_callback initializer, const napi_property_descriptor *methods, size_t num)
736     {
737         NAPI_ASSERT(env, exports != nullptr && initializer != nullptr && methods != nullptr, "Illegal export params");
738         // define class, provide the js-class members(property) and initializer.
739         napi_value ctor = nullptr;
740         NAPI_CALL(env, napi_define_class(env, name.data(), name.length(), initializer, nullptr, num, methods, &ctor));
741         NAPI_CALL(env, napi_set_named_property(env, exports, name.data(), ctor));
742         NAPI_CALL(env, MountJsConstructorToGlobal(env, type, ctor));
743 
744         if (type == TypeId::BY) {
745             // export global By-seed object (unbound, no metadata)
746             napi_value bySeed = nullptr;
747             NAPI_CALL(env, napi_new_instance(env, ctor, 0, nullptr, &bySeed));
748             NAPI_CALL(env, SetOrUpdateStrProp(env, bySeed, PROP_IS_SEED, "true")); // see seed-mark property
749             NAPI_CALL(env, napi_set_named_property(env, exports, "BY", bySeed));
750         }
751         return exports;
752     }
753 
754     /**Exports 'MatchValue' enumeration.*/
ExportMatchPattern(napi_env env,napi_value exports)755     static napi_value ExportMatchPattern(napi_env env, napi_value exports)
756     {
757         napi_value propMatchPattern;
758         napi_value propEquals;
759         napi_value propContains;
760         napi_value propStartsWith;
761         napi_value propEndsWith;
762         NAPI_CALL(env, napi_create_object(env, &propMatchPattern));
763         NAPI_CALL(env, napi_create_int32(env, ValueMatchRule::EQ, &propEquals));
764         NAPI_CALL(env, napi_create_int32(env, ValueMatchRule::CONTAINS, &propContains));
765         NAPI_CALL(env, napi_create_int32(env, ValueMatchRule::STARTS_WITH, &propStartsWith));
766         NAPI_CALL(env, napi_create_int32(env, ValueMatchRule::ENDS_WITH, &propEndsWith));
767         NAPI_CALL(env, napi_set_named_property(env, propMatchPattern, "EQUALS", propEquals));
768         NAPI_CALL(env, napi_set_named_property(env, propMatchPattern, "CONTAINS", propContains));
769         NAPI_CALL(env, napi_set_named_property(env, propMatchPattern, "STARTS_WITH", propStartsWith));
770         NAPI_CALL(env, napi_set_named_property(env, propMatchPattern, "ENDS_WITH", propEndsWith));
771         NAPI_CALL(env, napi_set_named_property(env, exports, "MatchPattern", propMatchPattern));
772         return exports;
773     }
774 
775     /**Exports 'By' class definition and member functions.*/
ExportBy(napi_env env,napi_value exports)776     static napi_value ExportBy(napi_env env, napi_value exports)
777     {
778         static constexpr char cppCreator[] = "WidgetSelector::<init>";
779         napi_property_descriptor methods[] = {
780             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::ID], ByAttrBuilder<UiAttr::ID>),
781             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::TEXT], ByAttrBuilder<UiAttr::TEXT>),
782             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::KEY], ByAttrBuilder<UiAttr::KEY>),
783             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::TYPE], ByAttrBuilder<UiAttr::TYPE>),
784             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::ENABLED], ByAttrBuilder<UiAttr::ENABLED>),
785             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::FOCUSED], ByAttrBuilder<UiAttr::FOCUSED>),
786             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::SELECTED], ByAttrBuilder<UiAttr::SELECTED>),
787             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::CLICKABLE], ByAttrBuilder<UiAttr::CLICKABLE>),
788             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::LONG_CLICKABLE], ByAttrBuilder<UiAttr::LONG_CLICKABLE>),
789             DECLARE_NAPI_FUNCTION(ATTR_NAMES[UiAttr::SCROLLABLE], ByAttrBuilder<UiAttr::SCROLLABLE>),
790             DECLARE_NAPI_FUNCTION("isBefore", ByRelBuilder<RelMode::IS_BEFORE>),
791             DECLARE_NAPI_FUNCTION("isAfter", ByRelBuilder<RelMode::IS_AFTER>)
792         };
793         static constexpr size_t num = sizeof(methods) / sizeof(methods[0]);
794         static constexpr napi_callback initializer = JsObjectInitializer<TypeId::BY>;
795         // StaticSyncCreator for internal usage, not exposed to 'By' class
796         gInternalByCreator = StaticSyncCreator<cppCreator, BY>;
797         return ExportClass(env, exports, "By", TypeId::BY, initializer, methods, num);
798     }
799 
800     /**Exports 'UiComponent' class definition and member functions.*/
ExportUiComponent(napi_env env,napi_value exports)801     napi_value ExportUiComponent(napi_env env, napi_value exports)
802     {
803         // UiComponent method calls will be forwarded to the bound UiDriver object
804         static constexpr char cppInput[] = "UiDriver::InputText";
805         static constexpr char cppSearch[] = "UiDriver::ScrollSearch";
806         static constexpr char cppDragTo[] = "UiDriver::DragWidgetToAnother";
807         static constexpr napi_property_descriptor methods[] = {
808             DECLARE_NAPI_FUNCTION("getId", ComponentAttrGetter<UiAttr::ID>),
809             DECLARE_NAPI_FUNCTION("getText", ComponentAttrGetter<UiAttr::TEXT>),
810             DECLARE_NAPI_FUNCTION("getKey", ComponentAttrGetter<UiAttr::KEY>),
811             DECLARE_NAPI_FUNCTION("getType", ComponentAttrGetter<UiAttr::TYPE>),
812             DECLARE_NAPI_FUNCTION("isEnabled", ComponentAttrGetter<UiAttr::ENABLED>),
813             DECLARE_NAPI_FUNCTION("isFocused", ComponentAttrGetter<UiAttr::FOCUSED>),
814             DECLARE_NAPI_FUNCTION("isSelected", ComponentAttrGetter<UiAttr::SELECTED>),
815             DECLARE_NAPI_FUNCTION("isClickable", ComponentAttrGetter<UiAttr::CLICKABLE>),
816             DECLARE_NAPI_FUNCTION("isLongClickable", ComponentAttrGetter<UiAttr::LONG_CLICKABLE>),
817             DECLARE_NAPI_FUNCTION("isScrollable", ComponentAttrGetter<UiAttr::SCROLLABLE>),
818             DECLARE_NAPI_FUNCTION("click", ComponentToucher<WidgetOp::CLICK>),
819             DECLARE_NAPI_FUNCTION("longClick", ComponentToucher<WidgetOp::LONG_CLICK>),
820             DECLARE_NAPI_FUNCTION("doubleClick", ComponentToucher<WidgetOp::DOUBLE_CLICK>),
821             DECLARE_NAPI_FUNCTION("inputText", (GenericComponentFunc<cppInput, TypeId::NONE, TypeId::STRING>)),
822             DECLARE_NAPI_FUNCTION("scrollSearch", (GenericComponentFunc<cppSearch, TypeId::COMPONENT, TypeId::BY>)),
823             DECLARE_NAPI_FUNCTION("dragTo", (GenericComponentFunc<cppDragTo, TypeId::NONE, TypeId::COMPONENT>))
824         };
825         static constexpr size_t num = sizeof(methods) / sizeof(methods[0]);
826         static constexpr napi_callback init = JsObjectInitializer<TypeId::COMPONENT>;
827         return ExportClass(env, exports, "UiComponent", TypeId::COMPONENT, init, methods, num);
828     }
829 
830     /**Exports 'UiDriver' class definition and member functions.*/
ExportUiDriver(napi_env env,napi_value exports)831     static napi_value ExportUiDriver(napi_env env, napi_value exports)
832     {
833         static constexpr char cppCreator[] = "UiDriver::<init>";
834         static constexpr char cppDelay[] = "UiDriver::DelayMs";
835         static constexpr char cppFinds[] = "UiDriver::FindWidgets";
836         static constexpr char cppCap[] = "UiDriver::TakeScreenCap";
837         static constexpr napi_property_descriptor methods[] = {
838             DECLARE_NAPI_STATIC_FUNCTION("create", (StaticSyncCreator<cppCreator, TypeId::DRIVER>)),
839             DECLARE_NAPI_FUNCTION("delayMs", (GenericAsyncFunc<cppDelay, TypeId::NONE, false, TypeId::INT>)),
840             DECLARE_NAPI_FUNCTION("findComponents", (GenericAsyncFunc<cppFinds, TypeId::COMPONENT, true, TypeId::BY>)),
841             DECLARE_NAPI_FUNCTION("findComponent", (GenericAsyncFunc<cppFinds, TypeId::COMPONENT, false, TypeId::BY>)),
842             DECLARE_NAPI_FUNCTION("screenCap", (GenericAsyncFunc<cppCap, TypeId::BOOL, false, TypeId::STRING>)),
843             DECLARE_NAPI_FUNCTION("assertComponentExist", UiDriverComponentExistAsserter),
844             DECLARE_NAPI_FUNCTION("pressBack", UiDriverKeyOperator<UiKey::BACK>),
845             DECLARE_NAPI_FUNCTION("triggerKey", UiDriverKeyOperator<UiKey::GENERIC>),
846             // raw coordinate based action methods
847             DECLARE_NAPI_FUNCTION("click", SinglePointToucher<PointerOp::CLICK_P>),
848             DECLARE_NAPI_FUNCTION("longClick", SinglePointToucher<PointerOp::LONG_CLICK_P>),
849             DECLARE_NAPI_FUNCTION("doubleClick", SinglePointToucher<PointerOp::DOUBLE_CLICK_P>),
850             DECLARE_NAPI_FUNCTION("swipe", SinglePointToucher<PointerOp::SWIPE_P>),
851             DECLARE_NAPI_FUNCTION("drag", SinglePointToucher<PointerOp::DRAG_P>),
852         };
853         static constexpr size_t num = sizeof(methods) / sizeof(methods[0]);
854         static constexpr napi_callback init = JsObjectInitializer<TypeId::DRIVER>;
855         return ExportClass(env, exports, "UiDriver", TypeId::DRIVER, init, methods, num);
856     }
857 
Export(napi_env env,napi_value exports)858     napi_value Export(napi_env env, napi_value exports)
859     {
860         LOG_I("Begin export uitest apis");
861 #ifdef __DOUBLE_FRAMEWORK__
862         napi_property_descriptor props[] = {
863             DECLARE_NAPI_STATIC_FUNCTION("setTransactFunc", SetTransactFunc),
864             DECLARE_NAPI_STATIC_FUNCTION("getAndIncreaseCount", GetAndIncreaseCount)
865         };
866         NAPI_CALL(env, napi_define_properties(env, exports, sizeof(props)/ sizeof(props[0]), props));
867 #else
868         // export transaction-environment lifecycle callbacks
869         napi_property_descriptor props[] = {
870             DECLARE_NAPI_STATIC_FUNCTION("setup", EnvironmentSetup),
871             DECLARE_NAPI_STATIC_FUNCTION("teardown", EnvironmentTeardown)
872         };
873         NAPI_CALL(env, napi_define_properties(env, exports, sizeof(props)/ sizeof(props[0]), props));
874 #endif
875         if (ExportMatchPattern(env, exports) == nullptr) {
876             return nullptr;
877         }
878         if (ExportBy(env, exports) == nullptr) {
879             return nullptr;
880         }
881         if (ExportUiComponent(env, exports) == nullptr) {
882             return nullptr;
883         }
884         if (ExportUiDriver(env, exports) == nullptr) {
885             return nullptr;
886         }
887         LOG_I("End export uitest apis");
888         return exports;
889     }
890 
891     static napi_module module = {
892         .nm_version = 1,
893         .nm_flags = 0,
894         .nm_filename = nullptr,
895         .nm_register_func = Export,
896         .nm_modname = "uitest",
897         .nm_priv = ((void *) 0),
898         .reserved = {0},
899     };
900 
RegisterModule(void)901     extern "C" __attribute__((constructor)) void RegisterModule(void)
902     {
903         napi_module_register(&module);
904     }
905 }
906 
907 #ifndef __DOUBLE_FRAMEWORK__
908 // put register functions out of namespace to ensure C-linkage
909 extern const char _binary_uitest_exporter_js_start[];
910 extern const char _binary_uitest_exporter_js_end[];
911 extern const char _binary_uitest_exporter_abc_start[];
912 extern const char _binary_uitest_exporter_abc_end[];
913 
914 extern "C" __attribute__((visibility("default")))
NAPI_uitest_GetJSCode(const char ** buf,int * bufLen)915 void NAPI_uitest_GetJSCode(const char **buf, int *bufLen)
916 {
917     if (buf != nullptr) {
918         *buf = _binary_uitest_exporter_js_start;
919     }
920     if (bufLen != nullptr) {
921         *bufLen = _binary_uitest_exporter_js_end - _binary_uitest_exporter_js_start;
922     }
923 }
924 
925 extern "C" __attribute__((visibility("default")))
NAPI_uitest_GetABCCode(const char ** buf,int * bufLen)926 void NAPI_uitest_GetABCCode(const char **buf, int *bufLen)
927 {
928     if (buf != nullptr) {
929         *buf = _binary_uitest_exporter_abc_start;
930     }
931     if (bufLen != nullptr) {
932         *bufLen = _binary_uitest_exporter_abc_end - _binary_uitest_exporter_abc_start;
933     }
934 }
935 #endif