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