How to use getValue method of com.intuit.karate.Match class

Best Karate code snippet using com.intuit.karate.Match.getValue

Source:ScenarioEngine.java Github

copy

Full Screen

...234 for (Map<String, String> map : rows) {235 Map<String, Object> row = new LinkedHashMap<>(map);236 List<String> toRemove = new ArrayList(map.size());237 for (Map.Entry<String, Object> entry : row.entrySet()) {238 String exp = (String) entry.getValue();239 Variable sv = evalKarateExpression(exp);240 if (sv.isNull() && !isWithinParentheses(241 exp)) { // by default empty / null will be stripped, force null like this: '(null)'242 toRemove.add(entry.getKey());243 } else {244 if (sv.isString()) {245 entry.setValue(sv.getAsString());246 } else { // map, list etc247 entry.setValue(sv.getValue());248 }249 }250 }251 for (String keyToRemove : toRemove) {252 row.remove(keyToRemove);253 }254 result.add(row);255 }256 setVariable(name.trim(), result);257 }258 public void replace(String name, String token, String value) {259 name = name.trim();260 Variable v = vars.get(name);261 if (v == null) {262 throw new RuntimeException("no variable found with name: " + name);263 }264 String text = v.getAsString();265 String replaced = replacePlaceholderText(text, token, value);266 setVariable(name, replaced);267 }268 public void assertTrue(String expression) {269 if (!evalJs(expression).isTrue()) {270 String message = "did not evaluate to 'true': " + expression;271 setFailedReason(new KarateException(message));272 }273 }274 public void print(String exp) {275 if (!config.isPrintEnabled()) {276 return;277 }278 evalJs("karate.log('[print]'," + exp + ")");279 }280 public void invokeAfterHookIfConfigured(boolean afterFeature) {281 if (runtime.caller.depth > 0) {282 return;283 }284 Variable v = afterFeature ? config.getAfterFeature() : config.getAfterScenario();285 if (v.isJsOrJavaFunction()) {286 if (afterFeature) {287 ScenarioEngine.set(this); // for any bridge / js to work288 }289 try {290 executeFunction(v);291 } catch (Exception e) {292 String prefix = afterFeature ? "afterFeature" : "afterScenario";293 logger.warn("{} hook failed: {}", prefix, e + "");294 }295 }296 }297 // gatling =================================================================298 //299 private PerfEvent prevPerfEvent;300 public void logLastPerfEvent(String failureMessage) {301 if (prevPerfEvent != null && runtime.perfMode) {302 if (failureMessage != null) {303 prevPerfEvent.setFailed(true);304 prevPerfEvent.setMessage(failureMessage);305 }306 runtime.featureRuntime.perfHook.reportPerfEvent(prevPerfEvent);307 }308 prevPerfEvent = null;309 }310 public void capturePerfEvent(PerfEvent event) {311 logLastPerfEvent(null);312 prevPerfEvent = event;313 }314 // http ====================================================================315 //316 private HttpRequestBuilder requestBuilder; // see init() method317 private HttpRequest request;318 private Response response;319 private Config config;320 public Config getConfig() {321 return config;322 }323 // important: use this to trigger client re-config324 // callonce routine is one example325 public void setConfig(Config config) {326 this.config = config;327 config.attach(JS);328 if (requestBuilder != null) {329 requestBuilder.client.setConfig(config);330 }331 }332 public HttpRequest getRequest() {333 return request;334 }335 public Response getResponse() {336 return response;337 }338 public HttpRequestBuilder getRequestBuilder() {339 return requestBuilder;340 }341 public void configure(String key, String exp) {342 Variable v = evalKarateExpression(exp);343 configure(key, v);344 }345 public Driver getDriver() {346 return this.driver;347 }348 public NewDriver getCurrentChrome() {349 if (driver != null && driver instanceof Chrome) {350 Optional<NewDriver> newDriver = newDrivers.values().stream().filter(item -> item.driver == driver).findFirst();351 if (newDriver.isPresent()) {352 return newDriver.get();353 } else {354 return new NewDriver(null, null, driver);355 }356 } else {357 return null;358 }359 }360 public void configure(String key, Variable v) {361 key = StringUtils.trimToEmpty(key);362 if (key.equals("newDriver")) {363 // @FIXME override start364 Map<String, Object> map = v.getValue();365 if (map.containsKey("name")) {366 String name = (String) map.get("name");367 validateVariableName(name);368 if (newDrivers.containsKey(name) || vars.containsKey(name)) {369 throw new RuntimeException("name: " + name + " already exists.");370 } else {371 Boolean isDefault = (boolean) map.getOrDefault("default", false);372 Driver newDriver = DriverOptions.start(map, runtime);373 newDrivers.put(name, new NewDriver(name, map, newDriver));374 if (isDefault) {375 config.configure("driver", v);376 setDriver(newDriver);377 }378 setVariable(name, newDriver);379 }380 } else {381 throw new RuntimeException("there must be a name when configure a new driver.");382 }383 // override end384 } else {385 // if next line returns true, config is http-client related386 if (config.configure(key, v)) {387 if (requestBuilder != null) {388 requestBuilder.client.setConfig(config);389 }390 }391 }392 }393 private void evalAsMap(String exp, BiConsumer<String, List<String>> fun) {394 Variable var = evalKarateExpression(exp);395 if (!var.isMap()) {396 logger.warn("did not evaluate to map {}: {}", exp, var);397 return;398 }399 Map<String, Object> map = var.getValue();400 map.forEach((k, v) -> {401 if (v instanceof List) {402 List list = (List) v;403 List<String> values = new ArrayList(list.size());404 for (Object o : list) { // support non-string values, e.g. numbers405 if (o != null) {406 values.add(o.toString());407 }408 }409 fun.accept(k, values);410 } else if (v != null) {411 fun.accept(k, Collections.singletonList(v.toString()));412 }413 });414 }415 public void url(String exp) {416 Variable var = evalKarateExpression(exp);417 requestBuilder.url(var.getAsString());418 }419 public void path(String exp) {420 List list = evalJs("[" + exp + "]").getValue();421 for (Object o : list) {422 if (o != null) {423 requestBuilder.path(o.toString());424 }425 }426 }427 // TODO document that for params and headers, lists are supported but428 // enclosed in square brackets429 public void param(String name, String exp) {430 Variable var = evalJs(exp);431 if (var.isList()) {432 requestBuilder.param(name, var.<List> getValue());433 } else {434 requestBuilder.param(name, var.getAsString());435 }436 }437 public void params(String expr) {438 evalAsMap(expr, (k, v) -> requestBuilder.param(k, v));439 }440 public void header(String name, String exp) {441 Variable var = evalKarateExpression(exp);442 if (var.isList()) {443 requestBuilder.header(name, var.<List> getValue());444 } else {445 requestBuilder.header(name, var.getAsString());446 }447 }448 public void headers(String expr) {449 evalAsMap(expr, (k, v) -> requestBuilder.header(k, v));450 }451 public void cookie(String name, String exp) {452 Variable var = evalKarateExpression(exp);453 if (var.isString()) {454 requestBuilder.cookie(name, var.getAsString());455 } else if (var.isMap()) {456 Map<String, Object> map = var.getValue();457 map.put("name", name);458 requestBuilder.cookie(map);459 }460 }461 // TODO document new options, [name = map | cookies listOfMaps]462 public void cookies(String exp) {463 Variable var = evalKarateExpression(exp);464 Map<String, Map> cookies = Cookies.normalize(var.getValue());465 requestBuilder.cookies(cookies.values());466 }467 private void updateConfigCookies(Map<String, Map> cookies) {468 if (cookies == null) {469 return;470 }471 if (config.getCookies().isNull()) {472 config.setCookies(new Variable(cookies));473 } else {474 Map<String, Map> map = getOrEvalAsMap(config.getCookies());475 map.putAll(cookies);476 config.setCookies(new Variable(map));477 }478 }479 public void formField(String name, String exp) {480 Variable var = evalKarateExpression(exp);481 if (var.isList()) {482 requestBuilder.formField(name, var.<List> getValue());483 } else {484 requestBuilder.formField(name, var.getAsString());485 }486 }487 public void formFields(String exp) {488 Variable var = evalKarateExpression(exp);489 if (var.isMap()) {490 Map<String, Object> map = var.getValue();491 map.forEach((k, v) -> {492 requestBuilder.formField(k, v);493 });494 } else {495 logger.warn("did not evaluate to map {}: {}", exp, var);496 }497 }498 public void multipartField(String name, String value) {499 multipartFile(name, value);500 }501 public void multipartFields(String exp) {502 multipartFiles(exp);503 }504 private void multiPartInternal(String name, Object value) {505 Map<String, Object> map = new HashMap();506 if (name != null) {507 map.put("name", name);508 }509 if (value instanceof Map) {510 map.putAll((Map) value);511 String toRead = (String) map.get("read");512 if (toRead != null) {513 File file = fileReader.relativePathToFile(toRead);514 map.put("value", file);515 }516 requestBuilder.multiPart(map);517 } else if (value instanceof String) {518 map.put("value", (String) value);519 multiPartInternal(name, map);520 } else if (value instanceof List) {521 List list = (List) value;522 for (Object o : list) {523 multiPartInternal(null, o);524 }525 } else if (logger.isTraceEnabled()) {526 logger.trace("did not evaluate to string, map or list {}: {}", name, value);527 }528 }529 public void multipartFile(String name, String exp) {530 Variable var = evalKarateExpression(exp);531 multiPartInternal(name, var.getValue());532 }533 public void multipartFiles(String exp) {534 Variable var = evalKarateExpression(exp);535 if (var.isMap()) {536 Map<String, Object> map = var.getValue();537 map.forEach((k, v) -> multiPartInternal(k, v));538 } else if (var.isList()) {539 List<Map> list = var.getValue();540 for (Map map : list) {541 multiPartInternal(null, map);542 }543 } else {544 logger.warn("did not evaluate to map or list {}: {}", exp, var);545 }546 }547 public void request(String body) {548 Variable v = evalKarateExpression(body);549 requestBuilder.body(v.getValue());550 }551 public void soapAction(String exp) {552 String action = evalKarateExpression(exp).getAsString();553 if (action == null) {554 action = "";555 }556 requestBuilder.header("SOAPAction", action);557 requestBuilder.contentType("text/xml");558 method("POST");559 }560 public void retry(String condition) {561 requestBuilder.setRetryUntil(condition);562 }563 public void method(String method) {564 if (!HttpConstants.HTTP_METHODS.contains(method.toUpperCase())) { // support expressions also565 method = evalKarateExpression(method).getAsString();566 }567 requestBuilder.method(method);568 httpInvoke();569 }570 // extracted for mock proceed()571 public Response httpInvoke() {572 if (requestBuilder.isRetry()) {573 httpInvokeWithRetries();574 } else {575 httpInvokeOnce();576 }577 requestBuilder.reset();578 return response;579 }580 private void httpInvokeOnce() {581 Map<String, Map> cookies = getOrEvalAsMap(config.getCookies());582 if (cookies != null) {583 requestBuilder.cookies(cookies.values());584 }585 Map<String, Object> headers;586 if (config.getHeaders().isJsOrJavaFunction()) {587 headers = getOrEvalAsMap(config.getHeaders(), requestBuilder.build());588 } else {589 headers = getOrEvalAsMap(config.getHeaders()); // avoid an extra http request build590 }591 if (headers != null) {592 requestBuilder.headers(headers);593 }594 request = requestBuilder.build();595 String perfEventName = null; // acts as a flag to report perf if not null596 if (runtime.perfMode) {597 perfEventName = runtime.featureRuntime.perfHook.getPerfEventName(request, runtime);598 }599 long startTime = System.currentTimeMillis();600 request.setStartTimeMillis(startTime); // this may be fine-adjusted by actual http client601 if (hooks != null) {602 hooks.forEach(h -> h.beforeHttpCall(request, runtime));603 }604 try {605 response = requestBuilder.client.invoke(request);606 } catch (Exception e) {607 long endTime = System.currentTimeMillis();608 long responseTime = endTime - startTime;609 String message = "http call failed after " + responseTime + " milliseconds for url: " + request.getUrl();610 logger.error(e.getMessage() + ", " + message);611 if (perfEventName != null) {612 PerfEvent pe = new PerfEvent(startTime, endTime, perfEventName, 0);613 capturePerfEvent(pe); // failure flag and message should be set by logLastPerfEvent()614 }615 throw new KarateException(message, e);616 }617 if (hooks != null) {618 hooks.forEach(h -> h.afterHttpCall(request, response, runtime));619 }620 byte[] bytes = response.getBody();621 Object body;622 String responseType;623 ResourceType resourceType = response.getResourceType();624 if (resourceType != null && resourceType.isBinary()) {625 responseType = "binary";626 body = bytes;627 } else {628 try {629 body = JsValue.fromBytes(bytes, true, resourceType);630 } catch (Exception e) {631 body = FileUtils.toString(bytes);632 logger.warn("auto-conversion of response failed: {}", e.getMessage());633 }634 if (body instanceof Map || body instanceof List) {635 responseType = "json";636 } else if (body instanceof Node) {637 responseType = "xml";638 } else {639 responseType = "string";640 }641 }642 setVariable(RESPONSE_STATUS, response.getStatus());643 setVariable(RESPONSE, body);644 if (config.isLowerCaseResponseHeaders()) {645 setVariable(RESPONSE_HEADERS, response.getHeadersWithLowerCaseNames());646 } else {647 setVariable(RESPONSE_HEADERS, response.getHeaders());648 }649 setHiddenVariable(RESPONSE_BYTES, bytes);650 setHiddenVariable(RESPONSE_TYPE, responseType);651 cookies = response.getCookies();652 updateConfigCookies(cookies);653 setHiddenVariable(RESPONSE_COOKIES, cookies);654 startTime = request.getStartTimeMillis(); // in case it was re-adjusted by http client655 long endTime = request.getEndTimeMillis();656 setHiddenVariable(REQUEST_TIME_STAMP, startTime);657 setHiddenVariable(RESPONSE_TIME, endTime - startTime);658 if (perfEventName != null) {659 PerfEvent pe = new PerfEvent(startTime, endTime, perfEventName, response.getStatus());660 capturePerfEvent(pe);661 }662 }663 private void httpInvokeWithRetries() {664 int maxRetries = config.getRetryCount();665 int sleep = config.getRetryInterval();666 int retryCount = 0;667 while (true) {668 if (retryCount == maxRetries) {669 throw new KarateException("too many retry attempts: " + maxRetries);670 }671 if (retryCount > 0) {672 try {673 logger.debug("sleeping before retry #{}", retryCount);674 Thread.sleep(sleep);675 } catch (Exception e) {676 throw new RuntimeException(e);677 }678 }679 httpInvokeOnce();680 Variable v;681 try {682 v = evalKarateExpression(requestBuilder.getRetryUntil());683 } catch (Exception e) {684 logger.warn("retry condition evaluation failed: {}", e.getMessage());685 v = Variable.NULL;686 }687 if (v.isTrue()) {688 if (retryCount > 0) {689 logger.debug("retry condition satisfied");690 }691 break;692 } else {693 logger.debug("retry condition not satisfied: {}", requestBuilder.getRetryUntil());694 }695 retryCount++;696 }697 }698 public void status(int status) {699 if (status != response.getStatus()) {700 // make sure log masking is applied701 String message = HttpLogger.getStatusFailureMessage(status, config, request, response);702 setFailedReason(new KarateException(message));703 }704 }705 public KeyStore getKeyStore(String trustStoreFile, String password, String type) {706 if (trustStoreFile == null) {707 return null;708 }709 char[] passwordChars = password == null ? null : password.toCharArray();710 if (type == null) {711 type = KeyStore.getDefaultType();712 }713 try {714 KeyStore keyStore = KeyStore.getInstance(type);715 InputStream is = fileReader.readFileAsStream(trustStoreFile);716 keyStore.load(is, passwordChars);717 logger.debug("key store key count for {}: {}", trustStoreFile, keyStore.size());718 return keyStore;719 } catch (Exception e) {720 logger.error("key store init failed: {}", e.getMessage());721 throw new RuntimeException(e);722 }723 }724 // http mock ===============================================================725 //726 public void mockProceed(String requestUrlBase) {727 String urlBase = requestUrlBase == null ? vars.get(REQUEST_URL_BASE).getValue() : requestUrlBase;728 String uri = vars.get(REQUEST_URI).getValue();729 String url = uri == null ? urlBase : urlBase + "/" + uri;730 requestBuilder.url(url);731 requestBuilder.params(vars.get(REQUEST_PARAMS).getValue());732 requestBuilder.method(vars.get(REQUEST_METHOD).getValue());733 requestBuilder.headers(vars.get(REQUEST_HEADERS).<Map> getValue());734 requestBuilder.removeHeader(HttpConstants.HDR_CONTENT_LENGTH);735 requestBuilder.body(vars.get(REQUEST).getValue());736 if (requestBuilder.client instanceof ArmeriaHttpClient) {737 Request mockRequest = MockHandler.LOCAL_REQUEST.get();738 if (mockRequest != null) {739 ArmeriaHttpClient client = (ArmeriaHttpClient) requestBuilder.client;740 client.setRequestContext(mockRequest.getRequestContext());741 }742 }743 httpInvoke();744 }745 public Map<String, Object> mockConfigureHeaders() {746 return getOrEvalAsMap(config.getResponseHeaders());747 }748 public void mockAfterScenario() {749 if (config.getAfterScenario().isJsOrJavaFunction()) {750 executeFunction(config.getAfterScenario());751 }752 }753 // websocket / async =======================================================754 //755 private List<WebSocketClient> webSocketClients;756 CompletableFuture SIGNAL = new CompletableFuture();757 public WebSocketClient webSocket(WebSocketOptions options) {758 WebSocketClient webSocketClient = new WebSocketClient(options, logger);759 if (webSocketClients == null) {760 webSocketClients = new ArrayList();761 }762 webSocketClients.add(webSocketClient);763 return webSocketClient;764 }765 public void signal(Object result) {766 logger.debug("signal called: {}", result);767 if (parent != null) {768 parent.signal(result);769 } else {770 synchronized (JS.context) {771 SIGNAL.complete(result);772 }773 }774 }775 public Object listen(String exp) {776 Variable v = evalKarateExpression(exp);777 int timeout = v.getAsInt();778 logger.debug("entered listen state with timeout: {}", timeout);779 Object listenResult = null;780 try {781 listenResult = SIGNAL.get(timeout, TimeUnit.MILLISECONDS);782 } catch (Exception e) {783 logger.error("listen timed out: {}", e + "");784 }785 synchronized (JS.context) {786 setHiddenVariable(LISTEN_RESULT, listenResult);787 logger.debug("exit listen state with result: {}", listenResult);788 SIGNAL = new CompletableFuture();789 return listenResult;790 }791 }792 public Command fork(boolean useLineFeed, List<String> args) {793 return fork(useLineFeed, Collections.singletonMap("args", args));794 }795 public Command fork(boolean useLineFeed, String line) {796 return fork(useLineFeed, Collections.singletonMap("line", line));797 }798 public Command fork(boolean useLineFeed, Map<String, Object> options) {799 Boolean useShell = (Boolean) options.get("useShell");800 if (useShell == null) {801 useShell = false;802 }803 List<String> list = (List) options.get("args");804 String[] args;805 if (list == null) {806 String line = (String) options.get("line");807 if (line == null) {808 throw new RuntimeException("'line' or 'args' is required");809 }810 args = Command.tokenize(line);811 } else {812 args = list.toArray(new String[list.size()]);813 }814 if (useShell) {815 args = Command.prefixShellArgs(args);816 }817 String workingDir = (String) options.get("workingDir");818 File workingFile = workingDir == null ? null : new File(workingDir);819 Command command = new Command(useLineFeed, logger, null, null, workingFile, args);820 Map env = (Map) options.get("env");821 if (env != null) {822 command.setEnvironment(env);823 }824 Boolean redirectErrorStream = (Boolean) options.get("redirectErrorStream");825 if (redirectErrorStream != null) {826 command.setRedirectErrorStream(redirectErrorStream);827 }828 Value funOut = (Value) options.get("listener");829 if (funOut != null && funOut.canExecute()) {830 ScenarioListener sl = new ScenarioListener(this, funOut);831 command.setListener(sl);832 }833 Value funErr = (Value) options.get("errorListener");834 if (funErr != null && funErr.canExecute()) {835 ScenarioListener sl = new ScenarioListener(this, funErr);836 command.setErrorListener(sl);837 }838 Boolean start = (Boolean) options.get("start");839 if (start == null) {840 start = true;841 }842 if (start) {843 command.start();844 }845 return command;846 }847 // ui driver / robot =======================================================848 //849 protected Driver driver;850 protected Plugin robot;851 private void autoDef(Plugin plugin, String instanceName) {852 for (String methodName : plugin.methodNames()) {853 String invoke = instanceName + "." + methodName;854 StringBuilder sb = new StringBuilder();855 sb.append("(function(){ if (arguments.length == 0) return ").append(invoke).append("();")856 .append(" if (arguments.length == 1) return ").append(invoke).append("(arguments[0]);")857 .append(" if (arguments.length == 2) return ").append(invoke).append("(arguments[0], arguments[1]);")858 .append(" if (arguments.length == 3) return ")859 .append(invoke).append("(arguments[0], arguments[1], arguments[2]);")860 .append(" if (arguments.length == 4) return ")861 .append(invoke).append("(arguments[0], arguments[1], arguments[2], arguments[3]);")862 .append(" if (arguments.length == 5) return ")863 .append(invoke).append("(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);")864 .append(" if (arguments.length == 6) return ")865 .append(invoke).append("(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);")866 .append(" if (arguments.length == 7) return ")867 .append(invoke)868 .append("(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]);")869 .append(" return ").append(invoke).append(870 "(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], "871 + "arguments[7]) })");872 setHiddenVariable(methodName, evalJs(sb.toString()));873 }874 }875 public void driver(String exp) {876 Variable v = evalKarateExpression(exp);877 // re-create driver within a test if needed878 // but user is expected to call quit() OR use the driver keyword with a JSON argument879 if (driver == null || driver.isTerminated() || v.isMap()) {880 Map<String, Object> options = config.getDriverOptions();881 if (options == null) {882 options = new HashMap();883 }884 options.put("target", config.getDriverTarget());885 if (v.isMap()) {886 options.putAll(v.getValue());887 }888 setDriver(DriverOptions.start(options, runtime));889 }890 if (v.isString()) {891 driver.setUrl(v.getAsString());892 }893 }894 public void robot(String exp) {895 Variable v = evalKarateExpression(exp);896 if (robot == null) {897 Map<String, Object> options = config.getRobotOptions();898 if (options == null) {899 options = new HashMap();900 }901 if (v.isMap()) {902 options.putAll(v.getValue());903 } else if (v.isString()) {904 options.put("window", v.getAsString());905 }906 try {907 Class clazz = Class.forName("com.intuit.karate.robot.RobotFactory");908 PluginFactory factory = (PluginFactory) clazz.newInstance();909 robot = factory.create(runtime, options);910 } catch (KarateException ke) {911 throw ke;912 } catch (Exception e) {913 String message =914 "cannot instantiate robot, is 'karate-robot' included as a maven / gradle dependency ? " + e.getMessage();915 logger.error(message);916 throw new RuntimeException(message, e);917 }918 setRobot(robot);919 }920 }921 public void setDriver(Driver driver) {922 this.driver = driver;923 setHiddenVariable(DRIVER, driver);924 if (robot != null) {925 logger.warn("'robot' is active, use 'driver.' prefix for driver methods");926 return;927 }928 autoDef(driver, DRIVER);929 setHiddenVariable(KEY, Key.INSTANCE);930 Ocr ocr = new Ocr(driver);931 setHiddenVariable(Ocr.ENGINE_KEY(), ocr);932 Img img = new Img(driver, ocr);933 setHiddenVariable(Img.ENGINE_KEY(), img);934 asura.ui.karate.plugins.System sys = new asura.ui.karate.plugins.System(driver, ocr, img, true);935 setHiddenVariable(sys.ENGINE_KEY(), sys);936 }937 public void setRobot(Plugin robot) { // TODO unify938 this.robot = robot;939 // robot.setContext(this);940 setHiddenVariable(ROBOT, robot);941 if (driver != null) {942 logger.warn("'driver' is active, use 'robot.' prefix for robot methods");943 return;944 }945 autoDef(robot, ROBOT);946 setHiddenVariable(KEY, Key.INSTANCE);947 }948 public void stop(StepResult lastStepResult) {949 if (runtime.caller.isSharedScope()) {950 // TODO life-cycle this hand off951 ScenarioEngine caller = runtime.caller.parentRuntime.engine;952 if (driver != null) { // a called feature inited the driver953 caller.setDriver(driver);954 }955 if (robot != null) {956 caller.setRobot(robot);957 }958 caller.webSocketClients = webSocketClients;959 // return, don't kill driver just yet960 } else if (runtime.caller.depth == 0) { // end of top-level scenario (no caller)961 if (webSocketClients != null) {962 webSocketClients.forEach(WebSocketClient::close);963 }964 if (driver != null) { // TODO move this to Plugin.afterScenario()965 DriverOptions options = driver.getOptions();966 if (options.stop) {967 driver.quit();968 }969 if (options.target != null) {970 logger.debug("custom target configured, attempting stop()");971 Map<String, Object> map = options.target.stop(runtime);972 String video = (String) map.get("video");973 embedVideo(video);974 } else {975 if (options.afterStop != null) {976 Command.execLine(null, options.afterStop);977 }978 embedVideo(options.videoFile);979 }980 }981 // @FIXME override start982 if (!newDrivers.isEmpty()) {983 newDrivers.values().forEach(newDriver -> {984 if (newDriver.isStop()) {985 newDriver.stopDriver();986 }987 });988 }989 // override end990 if (robot != null) {991 robot.afterScenario();992 }993 }994 }995 private void embedVideo(String path) {996 if (path != null) {997 File videoFile = new File(path);998 if (videoFile.exists()) {999 Embed embed = runtime.embedVideo(videoFile);1000 logger.debug("appended video to report: {}", embed);1001 }1002 }1003 }1004 // doc =====================================================================1005 //1006 private KarateTemplateEngine templateEngine;1007 public void doc(String exp) {1008 if (runtime.reportDisabled) {1009 return;1010 }1011 String path;1012 Variable v = evalKarateExpression(exp);1013 if (v.isString()) {1014 path = v.getAsString();1015 } else if (v.isMap()) {1016 Map<String, Object> map = v.getValue();1017 path = (String) map.get("read");1018 if (path == null) {1019 logger.warn("doc json missing 'read' property: {}", v);1020 return;1021 }1022 } else {1023 logger.warn("doc is not string or json: {}", v);1024 return;1025 }1026 if (templateEngine == null) {1027 String prefixedPath = runtime.featureRuntime.rootFeature.feature.getResource().getPrefixedParentPath();1028 templateEngine = TemplateUtils.forResourcePath(JS, prefixedPath);1029 }1030 String html = templateEngine.process(path);1031 runtime.embed(FileUtils.toBytes(html), ResourceType.HTML);1032 }1033 //==========================================================================1034 //1035 public void init() { // not in constructor because it has to be on Runnable.run() thread1036 JS = JsEngine.local();1037 logger.trace("js context: {}", JS);1038 runtime.magicVariables.forEach((k, v) -> setHiddenVariable(k, v));1039 attachVariables(); // re-hydrate any functions from caller or background1040 setHiddenVariable(KARATE, bridge);1041 setHiddenVariable(READ, readFunction);1042 HttpClient client = runtime.featureRuntime.suite.clientFactory.create(this);1043 requestBuilder = new HttpRequestBuilder(client);1044 // TODO improve life cycle and concept of shared objects1045 if (!runtime.caller.isNone()) {1046 ScenarioEngine caller = runtime.caller.parentRuntime.engine;1047 if (caller.driver != null) {1048 setDriver(caller.driver);1049 }1050 if (caller.robot != null) {1051 setRobot(caller.robot);1052 }1053 }1054 }1055 private void attachVariables() {1056 vars.forEach((k, v) -> {1057 switch (v.type) {1058 case JS_FUNCTION:1059 Value value = attach(v.getValue());1060 v = new Variable(value);1061 vars.put(k, v);1062 break;1063 case MAP:1064 case LIST:1065 recurseAndAttach(v.getValue());1066 break;1067 case OTHER:1068 if (v.isJsFunctionWrapper()) {1069 JsFunction jf = v.getValue();1070 Value attached = attachSource(jf.source);1071 v = new Variable(attached);1072 vars.put(k, v);1073 }1074 break;1075 default:1076 // do nothing1077 }1078 JS.put(k, v.getValue());1079 });1080 }1081 public Map<String, Variable> detachVariables() {1082 Map<String, Variable> detached = new HashMap(vars.size());1083 vars.forEach((k, v) -> {1084 switch (v.type) {1085 case JS_FUNCTION:1086 JsFunction jf = new JsFunction(v.getValue());1087 v = new Variable(jf);1088 break;1089 case MAP:1090 case LIST:1091 recurseAndDetach(v.getValue());1092 break;1093 default:1094 // do nothing1095 }1096 detached.put(k, v);1097 });1098 return detached;1099 }1100 protected Object recurseAndAttach(Object o) {1101 if (o instanceof Value) {1102 Value value = (Value) o;1103 return value.canExecute() ? attach(value) : null;1104 } else if (o instanceof JsFunction) {1105 JsFunction jf = (JsFunction) o;1106 return attachSource(jf.source);1107 } else if (o instanceof List) {1108 List list = (List) o;1109 int count = list.size();1110 for (int i = 0; i < count; i++) {1111 Object child = list.get(i);1112 Object result = recurseAndAttach(child);1113 if (result != null) {1114 list.set(i, result);1115 }1116 }1117 return null;1118 } else if (o instanceof Map) {1119 Map<String, Object> map = (Map) o;1120 map.forEach((k, v) -> {1121 Object result = recurseAndAttach(v);1122 if (result != null) {1123 map.put(k, result);1124 }1125 });1126 return null;1127 } else {1128 return null;1129 }1130 }1131 protected Object recurseAndDetach(Object o) {1132 if (o instanceof Value) {1133 Value value = (Value) o;1134 return value.canExecute() ? new JsFunction(value) : null;1135 } else if (o instanceof List) {1136 List list = (List) o;1137 int count = list.size();1138 for (int i = 0; i < count; i++) {1139 Object child = list.get(i);1140 Object result = recurseAndDetach(child);1141 if (result != null) {1142 list.set(i, result);1143 }1144 }1145 return null;1146 } else if (o instanceof Map) {1147 Map<String, Object> map = (Map) o;1148 map.forEach((k, v) -> {1149 Object result = recurseAndDetach(v);1150 if (result != null) {1151 map.put(k, result);1152 }1153 });1154 return null;1155 } else {1156 return null;1157 }1158 }1159 public Value attachSource(CharSequence source) {1160 return JS.attachSource(source);1161 }1162 public Value attach(Value before) {1163 return JS.attach(before);1164 }1165 protected <T> Map<String, T> getOrEvalAsMap(Variable var, Object... args) {1166 if (var.isJsOrJavaFunction()) {1167 Variable res = executeFunction(var, args);1168 return res.isMap() ? res.getValue() : null;1169 } else {1170 return var.isMap() ? var.getValue() : null;1171 }1172 }1173 public Variable executeFunction(Variable var, Object... args) {1174 switch (var.type) {1175 case JS_FUNCTION:1176 Value jsFunction = var.getValue();1177 JsValue jsResult = executeJsValue(jsFunction, args);1178 return new Variable(jsResult);1179 case JAVA_FUNCTION: // definitely a "call" with a single argument1180 Function javaFunction = var.getValue();1181 Object arg = args.length == 0 ? null : args[0];1182 Object javaResult = javaFunction.apply(arg);1183 return new Variable(JsValue.unWrap(javaResult));1184 default:1185 throw new RuntimeException("expected function, but was: " + var);1186 }1187 }1188 private JsValue executeJsValue(Value function, Object... args) {1189 try {1190 return JS.execute(function, args);1191 } catch (Exception e) {1192 String jsSource = function.getSourceLocation().getCharacters().toString();1193 KarateException ke = fromJsEvalException(jsSource, e);1194 setFailedReason(ke);1195 throw ke;1196 }1197 }1198 public Variable evalJs(String js) {1199 try {1200 return new Variable(JS.eval(js));1201 } catch (Exception e) {1202 KarateException ke = fromJsEvalException(js, e);1203 setFailedReason(ke);1204 throw ke;1205 }1206 }1207 protected static KarateException fromJsEvalException(String js, Exception e) {1208 // do our best to make js error traces informative, else thrown exception seems to1209 // get swallowed by the java reflection based method invoke flow1210 StackTraceElement[] stack = e.getStackTrace();1211 StringBuilder sb = new StringBuilder();1212 sb.append(">>>> js failed:\n");1213 List<String> lines = StringUtils.toStringLines(js);1214 int index = 0;1215 for (String line : lines) {1216 sb.append(String.format("%02d", ++index)).append(": ").append(line).append('\n');1217 }1218 sb.append("<<<<\n");1219 sb.append(e.toString()).append('\n');1220 for (int i = 0; i < stack.length; i++) {1221 String line = stack[i].toString();1222 sb.append("- ").append(line).append('\n');1223 if (line.startsWith("<js>") || i > 5) {1224 break;1225 }1226 }1227 return new KarateException(sb.toString());1228 }1229 public void setHiddenVariable(String key, Object value) {1230 if (value instanceof Variable) {1231 value = ((Variable) value).getValue();1232 }1233 JS.put(key, value);1234 }1235 public void setVariable(String key, Object value) {1236 Variable v;1237 if (value instanceof Variable) {1238 v = (Variable) value;1239 } else {1240 v = new Variable(value);1241 }1242 vars.put(key, v);1243 if (JS != null) {1244 JS.put(key, v.getValue());1245 }1246 if (children != null) {1247 children.forEach(c -> c.setVariable(key, value));1248 }1249 }1250 public void setVariables(Map<String, Object> map) {1251 if (map == null) {1252 return;1253 }1254 map.forEach((k, v) -> setVariable(k, v));1255 }1256 private static Map<String, Variable> copy(Map<String, Variable> source, boolean deep) {1257 Map<String, Variable> map = new HashMap(source.size());1258 source.forEach((k, v) -> map.put(k, v.copy(deep)));1259 return map;1260 }1261 public Map<String, Variable> copyVariables(boolean deep) {1262 return copy(vars, deep);1263 }1264 public Map<String, Object> getAllVariablesAsMap() {1265 Map<String, Object> map = new HashMap(vars.size());1266 vars.forEach((k, v) -> map.put(k, v == null ? null : v.getValue()));1267 return map;1268 }1269 private static void validateVariableName(String name) {1270 if (!isValidVariableName(name)) {1271 throw new RuntimeException("invalid variable name: " + name);1272 }1273 if (KARATE.equals(name)) {1274 throw new RuntimeException("'karate' is a reserved name");1275 }1276 if (REQUEST.equals(name) || "url".equals(name)) {1277 throw new RuntimeException(1278 "'" + name + "' is a reserved name, also use the form '* " + name + " <expression>' instead");1279 }1280 }1281 private Variable evalAndCastTo(AssignType assignType, String exp) {1282 Variable v = evalKarateExpression(exp);1283 switch (assignType) {1284 case BYTE_ARRAY:1285 return new Variable(v.getAsByteArray());1286 case STRING:1287 return new Variable(v.getAsString());1288 case XML:1289 return new Variable(v.getAsXml());1290 case XML_STRING:1291 String xml = XmlUtils.toString(v.getAsXml());1292 return new Variable(xml);1293 case JSON:1294 return new Variable(v.getValueAndForceParsingAsJson());1295 case YAML:1296 return new Variable(JsonUtils.fromYaml(v.getAsString()));1297 case CSV:1298 return new Variable(JsonUtils.fromCsv(v.getAsString()));1299 case COPY:1300 return v.copy(true);1301 default: // AUTO (TEXT is pre-handled, see below)1302 return v; // as is1303 }1304 }1305 public void assign(AssignType assignType, String name, String exp) {1306 name = StringUtils.trimToEmpty(name);1307 validateVariableName(name); // always validate when gherkin1308 if (vars.containsKey(name)) {1309 logger.warn("over-writing existing variable '{}' with new value: {}", name, exp);1310 }1311 if (assignType == AssignType.TEXT) {1312 setVariable(name, exp);1313 } else {1314 setVariable(name, evalAndCastTo(assignType, exp));1315 }1316 }1317 private static boolean isEmbeddedExpression(String text) {1318 return text != null && (text.startsWith("#(") || text.startsWith("##(")) && text.endsWith(")");1319 }1320 private static class EmbedAction {1321 final boolean remove;1322 final Object value;1323 private EmbedAction(boolean remove, Object value) {1324 this.remove = remove;1325 this.value = value;1326 }1327 static EmbedAction remove() {1328 return new EmbedAction(true, null);1329 }1330 static EmbedAction update(Object value) {1331 return new EmbedAction(false, value);1332 }1333 }1334 public Variable evalEmbeddedExpressions(Variable value) {1335 switch (value.type) {1336 case STRING:1337 case MAP:1338 case LIST:1339 EmbedAction ea = recurseEmbeddedExpressions(value);1340 if (ea != null) {1341 return ea.remove ? Variable.NULL : new Variable(ea.value);1342 } else {1343 return value;1344 }1345 case XML:1346 recurseXmlEmbeddedExpressions(value.getValue());1347 default:1348 return value;1349 }1350 }1351 private EmbedAction recurseEmbeddedExpressions(Variable node) {1352 switch (node.type) {1353 case LIST:1354 List list = node.getValue();1355 Set<Integer> indexesToRemove = new HashSet();1356 int count = list.size();1357 for (int i = 0; i < count; i++) {1358 EmbedAction ea = recurseEmbeddedExpressions(new Variable(list.get(i)));1359 if (ea != null) {1360 if (ea.remove) {1361 indexesToRemove.add(i);1362 } else {1363 list.set(i, ea.value);1364 }1365 }1366 }1367 if (!indexesToRemove.isEmpty()) {1368 List copy = new ArrayList(count - indexesToRemove.size());1369 for (int i = 0; i < count; i++) {1370 if (!indexesToRemove.contains(i)) {1371 copy.add(list.get(i));1372 }1373 }1374 return EmbedAction.update(copy);1375 } else {1376 return null;1377 }1378 case MAP:1379 Map<String, Object> map = node.getValue();1380 List<String> keysToRemove = new ArrayList();1381 map.forEach((k, v) -> {1382 EmbedAction ea = recurseEmbeddedExpressions(new Variable(v));1383 if (ea != null) {1384 if (ea.remove) {1385 keysToRemove.add(k);1386 } else {1387 map.put(k, ea.value);1388 }1389 }1390 });1391 for (String key : keysToRemove) {1392 map.remove(key);1393 }1394 return null;1395 case XML:1396 return null;1397 case STRING:1398 String value = StringUtils.trimToNull(node.getValue());1399 if (!isEmbeddedExpression(value)) {1400 return null;1401 }1402 boolean optional = value.charAt(1) == '#';1403 value = value.substring(optional ? 2 : 1);1404 try {1405 JsValue result = JS.eval(value);1406 if (optional) {1407 if (result.isNull()) {1408 return EmbedAction.remove();1409 } else if (result.isObject() || result.isArray()) {1410 // preserve optional JSON chunk schema-like references as-is, they are needed for future match attempts1411 return null;1412 }1413 // and only substitute primitives !1414 }1415 return EmbedAction.update(result.getValue());1416 } catch (Exception e) {1417 logger.trace("embedded expression failed {}: {}", value, e.getMessage());1418 return null;1419 }1420 default:1421 // do nothing1422 return null;1423 }1424 }1425 private void recurseXmlEmbeddedExpressions(Node node) {1426 if (node.getNodeType() == Node.DOCUMENT_NODE) {1427 node = node.getFirstChild();1428 }1429 NamedNodeMap attribs = node.getAttributes();1430 int attribCount = attribs == null ? 0 : attribs.getLength();1431 Set<Attr> attributesToRemove = new HashSet(attribCount);1432 for (int i = 0; i < attribCount; i++) {1433 Attr attrib = (Attr) attribs.item(i);1434 String value = attrib.getValue();1435 value = StringUtils.trimToNull(value);1436 if (isEmbeddedExpression(value)) {1437 boolean optional = value.charAt(1) == '#';1438 value = value.substring(optional ? 2 : 1);1439 try {1440 JsValue jv = JS.eval(value);1441 if (optional && jv.isNull()) {1442 attributesToRemove.add(attrib);1443 } else {1444 attrib.setValue(jv.getAsString());1445 }1446 } catch (Exception e) {1447 logger.trace("xml-attribute embedded expression failed, {}: {}", attrib.getName(), e.getMessage());1448 }1449 }1450 }1451 for (Attr toRemove : attributesToRemove) {1452 attribs.removeNamedItem(toRemove.getName());1453 }1454 NodeList nodeList = node.getChildNodes();1455 int childCount = nodeList.getLength();1456 List<Node> nodes = new ArrayList(childCount);1457 for (int i = 0; i < childCount; i++) {1458 nodes.add(nodeList.item(i));1459 }1460 Set<Node> elementsToRemove = new HashSet(childCount);1461 for (Node child : nodes) {1462 String value = child.getNodeValue();1463 if (value != null) {1464 value = StringUtils.trimToEmpty(value);1465 if (isEmbeddedExpression(value)) {1466 boolean optional = value.charAt(1) == '#';1467 value = value.substring(optional ? 2 : 1);1468 try {1469 JsValue jv = JS.eval(value);1470 if (optional) {1471 if (jv.isNull()) {1472 elementsToRemove.add(child);1473 } else if (jv.isXml() || jv.isObject()) {1474 // preserve optional XML chunk schema-like references as-is, they are needed for future match attempts1475 } else {1476 child.setNodeValue(jv.getAsString());1477 }1478 } else {1479 if (jv.isXml() || jv.isObject()) {1480 Node evalNode = jv.isXml() ? jv.getValue() : XmlUtils.fromMap(jv.getValue());1481 if (evalNode.getNodeType() == Node.DOCUMENT_NODE) {1482 evalNode = evalNode.getFirstChild();1483 }1484 if (child.getNodeType() == Node.CDATA_SECTION_NODE) {1485 child.setNodeValue(XmlUtils.toString(evalNode));1486 } else {1487 evalNode = node.getOwnerDocument().importNode(evalNode, true);1488 child.getParentNode().replaceChild(evalNode, child);1489 }1490 } else {1491 child.setNodeValue(jv.getAsString());1492 }1493 }1494 } catch (Exception e) {1495 logger.trace("xml embedded expression failed, {}: {}", child.getNodeName(), e.getMessage());1496 }1497 }1498 } else if (child.hasChildNodes() || child.hasAttributes()) {1499 recurseXmlEmbeddedExpressions(child);1500 }1501 }1502 for (Node toRemove : elementsToRemove) { // because of how the above routine works, these are always of type1503 // TEXT_NODE1504 Node parent = toRemove.getParentNode(); // element containing the text-node1505 Node grandParent = parent.getParentNode(); // parent element1506 grandParent.removeChild(parent);1507 }1508 }1509 public String replacePlaceholderText(String text, String token, String replaceWith) {1510 if (text == null) {1511 return null;1512 }1513 replaceWith = StringUtils.trimToNull(replaceWith);1514 if (replaceWith == null) {1515 return text;1516 }1517 try {1518 Variable v = evalKarateExpression(replaceWith);1519 replaceWith = v.getAsString();1520 } catch (Exception e) {1521 throw new RuntimeException(1522 "expression error (replace string values need to be within quotes): " + e.getMessage());1523 }1524 if (replaceWith == null) { // ignore if eval result is null1525 return text;1526 }1527 token = StringUtils.trimToNull(token);1528 if (token == null) {1529 return text;1530 }1531 char firstChar = token.charAt(0);1532 if (Character.isLetterOrDigit(firstChar)) {1533 token = '<' + token + '>';1534 }1535 return text.replace(token, replaceWith);1536 }1537 private static final String TOKEN = "token";1538 public void replaceTable(String text, List<Map<String, String>> list) {1539 if (text == null) {1540 return;1541 }1542 if (list == null) {1543 return;1544 }1545 for (Map<String, String> map : list) {1546 String token = map.get(TOKEN);1547 if (token == null) {1548 continue;1549 }1550 // the verbosity below is to be lenient with table second column name1551 List<String> keys = new ArrayList(map.keySet());1552 keys.remove(TOKEN);1553 Iterator<String> iterator = keys.iterator();1554 if (iterator.hasNext()) {1555 String key = keys.iterator().next();1556 String value = map.get(key);1557 replace(text, token, value);1558 }1559 }1560 }1561 public void set(String name, String path, Variable value) {1562 set(name, path, false, value, false, false);1563 }1564 private void set(String name, String path, String exp, boolean delete, boolean viaTable) {1565 set(name, path, isWithinParentheses(exp), evalKarateExpression(exp), delete, viaTable);1566 }1567 private void set(String name, String path, boolean isWithinParentheses, Variable value, boolean delete,1568 boolean viaTable) {1569 name = StringUtils.trimToEmpty(name);1570 path = StringUtils.trimToNull(path);1571 if (viaTable && value.isNull() && !isWithinParentheses) {1572 // by default, skip any expression that evaluates to null unless the user expressed1573 // intent to over-ride by enclosing the expression in parentheses1574 return;1575 }1576 if (path == null) {1577 StringUtils.Pair nameAndPath = parseVariableAndPath(name);1578 name = nameAndPath.left;1579 path = nameAndPath.right;1580 }1581 Variable target = vars.get(name);1582 if (isXmlPath(path)) {1583 if (target == null || target.isNull()) {1584 if (viaTable) { // auto create if using set via cucumber table as a convenience1585 Document empty = XmlUtils.newDocument();1586 target = new Variable(empty);1587 setVariable(name, target);1588 } else {1589 throw new RuntimeException("variable is null or not set '" + name + "'");1590 }1591 }1592 Document doc = target.getValue();1593 if (delete) {1594 XmlUtils.removeByPath(doc, path);1595 } else if (value.isXml()) {1596 Node node = value.getValue();1597 XmlUtils.setByPath(doc, path, node);1598 } else if (value.isMap()) { // cast to xml1599 Node node = XmlUtils.fromMap(value.getValue());1600 XmlUtils.setByPath(doc, path, node);1601 } else {1602 XmlUtils.setByPath(doc, path, value.getAsString());1603 }1604 } else { // assume json-path1605 if (target == null || target.isNull()) {1606 if (viaTable) { // auto create if using set via cucumber table as a convenience1607 Json json;1608 if (path.startsWith("$[") && !path.startsWith("$['")) {1609 json = Json.of("[]");1610 } else {1611 json = Json.of("{}");1612 }1613 target = new Variable(json.value());1614 setVariable(name, target);1615 } else {1616 throw new RuntimeException("variable is null or not set '" + name + "'");1617 }1618 }1619 Json json;1620 if (target.isMapOrList()) {1621 json = Json.of(target.<Object> getValue());1622 } else {1623 throw new RuntimeException("cannot set json path on type: " + target);1624 }1625 if (delete) {1626 json.remove(path);1627 } else {1628 json.set(path, value.<Object> getValue());1629 }1630 }1631 }1632 private static final String PATH = "path";1633 public void setViaTable(String name, String path, List<Map<String, String>> list) {1634 name = StringUtils.trimToEmpty(name);1635 path = StringUtils.trimToNull(path);1636 if (path == null) {1637 StringUtils.Pair nameAndPath = parseVariableAndPath(name);1638 name = nameAndPath.left;1639 path = nameAndPath.right;1640 }1641 for (Map<String, String> map : list) {1642 String append = (String) map.get(PATH);1643 if (append == null) {1644 continue;1645 }1646 List<String> keys = new ArrayList(map.keySet());1647 keys.remove(PATH);1648 int columnCount = keys.size();1649 for (int i = 0; i < columnCount; i++) {1650 String key = keys.get(i);1651 String expression = StringUtils.trimToNull(map.get(key));1652 if (expression == null) { // cucumber cell was left blank1653 continue; // skip1654 // default behavior is to skip nulls when the expression evaluates1655 // this is driven by the routine in setValueByPath1656 // and users can over-ride this by simply enclosing the expression in parentheses1657 }1658 String suffix;1659 try {1660 int arrayIndex = Integer.valueOf(key);1661 suffix = "[" + arrayIndex + "]";1662 } catch (NumberFormatException e) { // default to the column position as the index1663 suffix = columnCount > 1 ? "[" + i + "]" : "";1664 }1665 String finalPath;1666 if (append.startsWith("/") || (path != null && path.startsWith("/"))) { // XML1667 if (path == null) {1668 finalPath = append + suffix;1669 } else {1670 finalPath = path + suffix + '/' + append;1671 }1672 } else {1673 if (path == null) {1674 path = "$";1675 }1676 finalPath = path + suffix + '.' + append;1677 }1678 set(name, finalPath, expression, false, true);1679 }1680 }1681 }1682 public static StringUtils.Pair parseVariableAndPath(String text) {1683 Matcher matcher = VAR_AND_PATH_PATTERN.matcher(text);1684 matcher.find();1685 String name = text.substring(0, matcher.end());1686 String path;1687 if (matcher.end() == text.length()) {1688 path = "";1689 } else {1690 path = text.substring(matcher.end()).trim();1691 }1692 if (isXmlPath(path) || isXmlPathFunction(path)) {1693 // xml, don't prefix for json1694 } else {1695 path = "$" + path;1696 }1697 return StringUtils.pair(name, path);1698 }1699 public Match.Result match(Match.Type matchType, String expression, String path, String rhs) {1700 String name = StringUtils.trimToEmpty(expression);1701 if (isDollarPrefixedJsonPath(name) || isXmlPath(name)) { //1702 path = name;1703 name = RESPONSE;1704 }1705 if (name.startsWith("$")) { // in case someone used the dollar prefix by mistake on the LHS1706 name = name.substring(1);1707 }1708 path = StringUtils.trimToNull(path);1709 if (path == null) {1710 StringUtils.Pair pair = parseVariableAndPath(name);1711 name = pair.left;1712 path = pair.right;1713 }1714 if ("header".equals(name)) { // convenience shortcut for asserting against response header1715 return matchHeader(matchType, path, rhs);1716 }1717 Variable actual;1718 // karate started out by "defaulting" to JsonPath on the LHS of a match so we have this kludge1719 // but we now handle JS expressions of almost any shape on the LHS, if in doubt, wrap in parentheses1720 // actually it is not too bad - the XPath function check is the only odd one out1721 // rules:1722 // if not XPath function, wrapped in parentheses, involves function call1723 // [then] JS eval1724 // else if XPath, JsonPath, JsonPath wildcard ".." or "*" or "[?"1725 // [then] eval name, and do a JsonPath or XPath using the parsed path1726 if (isXmlPathFunction(path)1727 || (!name.startsWith("(") && !path.endsWith(")") && !path.contains(")."))1728 && (isDollarPrefixed(path) || isJsonPath(path) || isXmlPath(path))) {1729 actual = evalKarateExpression(name);1730 // edge case: java property getter, e.g. "driver.cookies"1731 if (!actual.isMap() && !actual.isList() && !isXmlPath(path) && !isXmlPathFunction(path)) {1732 actual = evalKarateExpression(expression); // fall back to JS eval of entire LHS1733 path = "$";1734 }1735 } else {1736 actual = evalKarateExpression(expression); // JS eval of entire LHS1737 path = "$";1738 }1739 if ("$".equals(path) || "/".equals(path)) {1740 // we have eval-ed the entire LHS, so proceed to match RHS to "$"1741 } else {1742 if (isDollarPrefixed(path)) { // json-path1743 actual = evalJsonPath(actual, path);1744 } else { // xpath1745 actual = evalXmlPath(actual, path);1746 }1747 }1748 Variable expected = evalKarateExpression(rhs);1749 return match(matchType, actual.getValue(), expected.getValue());1750 }1751 // TODO document that match header is case-insensitive at last1752 private Match.Result matchHeader(Match.Type matchType, String name, String exp) {1753 Variable expected = evalKarateExpression(exp);1754 String actual = response.getHeader(name);1755 return match(matchType, actual, expected.getValue());1756 }1757 public Match.Result match(Match.Type matchType, Object actual, Object expected) {1758 return Match.execute(JS, matchType, actual, expected);1759 }1760 private static final Pattern VAR_AND_PATH_PATTERN = Pattern.compile("\\w+");1761 private static final String VARIABLE_PATTERN_STRING = "[a-zA-Z][\\w]*";1762 private static final Pattern VARIABLE_PATTERN = Pattern.compile(VARIABLE_PATTERN_STRING);1763 private static final Pattern FUNCTION_PATTERN = Pattern.compile("^function[^(]*\\(");1764 private static final Pattern JS_PLACEHODER = Pattern.compile("\\$\\{.*?\\}");1765 public static boolean isJavaScriptFunction(String text) {1766 return FUNCTION_PATTERN.matcher(text).find();1767 }1768 public static boolean isValidVariableName(String name) {1769 return VARIABLE_PATTERN.matcher(name).matches();1770 }1771 public static boolean hasJavaScriptPlacehoder(String exp) {1772 return JS_PLACEHODER.matcher(exp).find();1773 }1774 public static final boolean isVariableAndSpaceAndPath(String text) {1775 return text.matches("^" + VARIABLE_PATTERN_STRING + "\\s+.+");1776 }1777 public static final boolean isVariable(String text) {1778 return VARIABLE_PATTERN.matcher(text).matches();1779 }1780 public static final boolean isWithinParentheses(String text) {1781 return text != null && text.startsWith("(") && text.endsWith(")");1782 }1783 public static final boolean isCallSyntax(String text) {1784 return text.startsWith("call ");1785 }1786 public static final boolean isCallOnceSyntax(String text) {1787 return text.startsWith("callonce ");1788 }1789 public static final boolean isGetSyntax(String text) {1790 return text.startsWith("get ") || text.startsWith("get[");1791 }1792 public static final boolean isJson(String text) {1793 return text.startsWith("{") || text.startsWith("[");1794 }1795 public static final boolean isXml(String text) {1796 return text.startsWith("<");1797 }1798 public static boolean isXmlPath(String text) {1799 return text.startsWith("/");1800 }1801 public static boolean isXmlPathFunction(String text) {1802 return text.matches("^[a-z-]+\\(.+");1803 }1804 public static final boolean isJsonPath(String text) {1805 return text.indexOf('*') != -1 || text.contains("..") || text.contains("[?");1806 }1807 public static final boolean isDollarPrefixed(String text) {1808 return text.startsWith("$");1809 }1810 public static final boolean isDollarPrefixedJsonPath(String text) {1811 return text.startsWith("$.") || text.startsWith("$[") || text.equals("$");1812 }1813 public static StringUtils.Pair parseCallArgs(String line) {1814 int pos = line.indexOf("read(");1815 if (pos != -1) {1816 pos = line.indexOf(')');1817 if (pos == -1) {1818 throw new RuntimeException("failed to parse call arguments: " + line);1819 }1820 return new StringUtils.Pair(line.substring(0, pos + 1), StringUtils.trimToNull(line.substring(pos + 1)));1821 }1822 pos = line.indexOf(' ');1823 if (pos == -1) {1824 return new StringUtils.Pair(line, null);1825 }1826 return new StringUtils.Pair(line.substring(0, pos), StringUtils.trimToNull(line.substring(pos)));1827 }1828 public Variable call(Variable called, Variable arg, boolean sharedScope) {1829 switch (called.type) {1830 case JS_FUNCTION:1831 case JAVA_FUNCTION:1832 return arg == null ? executeFunction(called) : executeFunction(called, new Object[] {arg.getValue()});1833 case FEATURE:1834 Variable res = callFeature(called.getValue(), arg, -1, sharedScope);1835 recurseAndAttach(res.getValue()); // will always be a map, we update entries within1836 return res;1837 default:1838 throw new RuntimeException("not a callable feature or js function: " + called);1839 }1840 }1841 public Variable call(boolean callOnce, String exp, boolean sharedScope) {1842 StringUtils.Pair pair = parseCallArgs(exp);1843 Variable called = evalKarateExpression(pair.left);1844 Variable arg = pair.right == null ? null : evalKarateExpression(pair.right);1845 Variable result;1846 if (callOnce) {1847 result = callOnce(exp, called, arg, sharedScope);1848 } else {1849 result = call(called, arg, sharedScope);1850 }1851 if (sharedScope && result.isMap()) {1852 setVariables(result.getValue());1853 }1854 return result;1855 }1856 private Variable result(ScenarioCall.Result result, boolean sharedScope) {1857 if (sharedScope) { // if shared scope1858 vars.clear(); // clean slate1859 vars.putAll(copy(result.vars, false)); // clone for safety1860 init(); // this will also insert magic variables1861 setConfig(new Config(result.config)); // re-apply config from time of snapshot1862 return Variable.NULL; // since we already reset the vars above we return null1863 // else the call() routine would try to do it again1864 // note that shared scope means a return value is meaningless1865 } else {1866 return result.value.copy(false); // clone result for safety1867 }1868 }1869 private Variable callOnce(String cacheKey, Variable called, Variable arg, boolean sharedScope) {1870 // IMPORTANT: the call result is always shallow-cloned before returning1871 // so that call result (especially if a java Map) is not mutated by other scenarios1872 final Map<String, ScenarioCall.Result> CACHE = runtime.featureRuntime.FEATURE_CACHE;1873 ScenarioCall.Result result = CACHE.get(cacheKey);1874 if (result != null) {1875 logger.trace("callonce cache hit for: {}", cacheKey);1876 return result(result, sharedScope);1877 }1878 long startTime = System.currentTimeMillis();1879 logger.trace("callonce waiting for lock: {}", cacheKey);1880 synchronized (CACHE) {1881 result = CACHE.get(cacheKey); // retry1882 if (result != null) {1883 long endTime = System.currentTimeMillis() - startTime;1884 logger.warn("this thread waited {} milliseconds for callonce lock: {}", endTime, cacheKey);1885 return result(result, sharedScope);1886 }1887 // this thread is the 'winner'1888 logger.info(">> lock acquired, begin callonce: {}", cacheKey);1889 Variable resultValue = call(called, arg, sharedScope);1890 // we clone result (and config) here, to snapshot state at the point the callonce was invoked1891 // this prevents the state from being clobbered by the subsequent steps of this1892 // first scenario that is about to use the result1893 Map<String, Variable> clonedVars = called.isFeature() && sharedScope ? detachVariables() : null;1894 Config clonedConfig = new Config(config);1895 clonedConfig.detach();1896 result = new ScenarioCall.Result(resultValue.copy(false), clonedConfig, clonedVars);1897 CACHE.put(cacheKey, result);1898 logger.info("<< lock released, cached callonce: {}", cacheKey);1899 return resultValue; // another routine will apply globally if needed1900 }1901 }1902 public Variable callFeature(Feature feature, Variable arg, int index, boolean sharedScope) {1903 if (arg == null || arg.isMap()) {1904 ScenarioCall call = new ScenarioCall(runtime, feature, arg);1905 call.setLoopIndex(index);1906 call.setSharedScope(sharedScope);1907 FeatureRuntime fr = new FeatureRuntime(call);1908 fr.run();1909 // VERY IMPORTANT ! switch back from called feature js context1910 THREAD_LOCAL.set(this);1911 FeatureResult result = fr.result;1912 runtime.addCallResult(result);1913 if (result.isFailed()) {1914 KarateException ke = result.getErrorMessagesCombined();1915 throw ke;1916 } else {1917 return new Variable(result.getVariables());1918 }1919 } else if (arg.isList() || arg.isJsOrJavaFunction()) {1920 List result = new ArrayList();1921 List<String> errors = new ArrayList();1922 int loopIndex = 0;1923 boolean isList = arg.isList();1924 Iterator iterator = isList ? arg.<List> getValue().iterator() : null;1925 while (true) {1926 Variable loopArg;1927 if (isList) {1928 loopArg = iterator.hasNext() ? new Variable(iterator.next()) : Variable.NULL;1929 } else { // function1930 loopArg = executeFunction(arg, new Object[] {loopIndex});1931 }1932 if (!loopArg.isMap()) {1933 if (!isList) {1934 logger.info("feature call loop function ended at index {}, returned: {}", loopIndex, loopArg);1935 }1936 break;1937 }1938 try {1939 Variable loopResult = callFeature(feature, loopArg, loopIndex, sharedScope);1940 result.add(loopResult.getValue());1941 } catch (Exception e) {1942 String message = "feature call loop failed at index: " + loopIndex + ", " + e.getMessage();1943 errors.add(message);1944 runtime.logError(message);1945 if (!isList) { // this is a generator function, abort infinite loop !1946 break;1947 }1948 }1949 loopIndex++;1950 }1951 if (errors.isEmpty()) {1952 return new Variable(result);1953 } else {1954 String errorMessage = StringUtils.join(errors, '\n');1955 throw new KarateException(errorMessage);1956 }1957 } else {1958 throw new RuntimeException("feature call argument is not a json object or array: " + arg);1959 }1960 }1961 public Variable evalJsonPath(Variable v, String path) {1962 Json json = Json.of(v.getValueAndForceParsingAsJson());1963 try {1964 return new Variable(json.get(path));1965 } catch (PathNotFoundException e) {1966 return Variable.NOT_PRESENT;1967 }1968 }1969 public static Variable evalXmlPath(Variable xml, String path) {1970 NodeList nodeList;1971 Node doc = xml.getAsXml();1972 try {1973 nodeList = XmlUtils.getNodeListByPath(doc, path);1974 } catch (Exception e) {1975 // hack, this happens for xpath functions that don't return nodes (e.g. count)1976 String strValue = XmlUtils.getTextValueByPath(doc, path);1977 Variable v = new Variable(strValue);1978 if (path.startsWith("count")) { // special case1979 return new Variable(v.getAsInt());1980 } else {1981 return v;1982 }1983 }1984 int count = nodeList.getLength();1985 if (count == 0) { // xpath / node does not exist !1986 return Variable.NOT_PRESENT;1987 }1988 if (count == 1) {1989 return nodeToValue(nodeList.item(0));1990 }1991 List list = new ArrayList();1992 for (int i = 0; i < count; i++) {1993 Variable v = nodeToValue(nodeList.item(i));1994 list.add(v.getValue());1995 }1996 return new Variable(list);1997 }1998 private static Variable nodeToValue(Node node) {1999 int childElementCount = XmlUtils.getChildElementCount(node);2000 if (childElementCount == 0) {2001 // hack assuming this is the most common "intent"2002 return new Variable(node.getTextContent());2003 }2004 if (node.getNodeType() == Node.DOCUMENT_NODE) {2005 return new Variable(node);2006 } else { // make sure we create a fresh doc else future xpath would run against original root2007 return new Variable(XmlUtils.toNewDocument(node));2008 }2009 }2010 public Variable evalJsonPathOnVariableByName(String name, String path) {2011 return evalJsonPath(vars.get(name), path);2012 }2013 public Variable evalXmlPathOnVariableByName(String name, String path) {2014 return evalXmlPath(vars.get(name), path);2015 }2016 public Variable evalKarateExpression(String text) {2017 text = StringUtils.trimToNull(text);2018 if (text == null) {2019 return Variable.NULL;2020 }2021 // don't re-evaluate if this is clearly a direct reference to a variable2022 // this avoids un-necessary conversion of xml into a map in some cases2023 // e.g. 'Given request foo' - where foo is a Variable of type XML2024 if (vars.containsKey(text)) {2025 return vars.get(text);2026 }2027 boolean callOnce = isCallOnceSyntax(text);2028 if (callOnce || isCallSyntax(text)) { // special case in form "callBegin foo arg"2029 if (callOnce) {2030 text = text.substring(9);2031 } else {2032 text = text.substring(5);2033 }2034 return call(callOnce, text, false);2035 } else if (isDollarPrefixedJsonPath(text)) {2036 return evalJsonPathOnVariableByName(RESPONSE, text);2037 } else if (isGetSyntax(text) || isDollarPrefixed(text)) { // special case in form2038 // get json[*].path2039 // $json[*].path2040 // get /xml/path2041 // get xpath-function(expression)2042 int index = -1;2043 if (text.startsWith("$")) {2044 text = text.substring(1);2045 } else if (text.startsWith("get[")) {2046 int pos = text.indexOf(']');2047 index = Integer.valueOf(text.substring(4, pos));2048 text = text.substring(pos + 2);2049 } else {2050 text = text.substring(4);2051 }2052 String left;2053 String right;2054 if (isDollarPrefixedJsonPath(text)) { // edge case get[0] $..foo2055 left = RESPONSE;2056 right = text;2057 } else if (isVariableAndSpaceAndPath(text)) {2058 int pos = text.indexOf(' ');2059 right = text.substring(pos + 1);2060 left = text.substring(0, pos);2061 } else {2062 StringUtils.Pair pair = parseVariableAndPath(text);2063 left = pair.left;2064 right = pair.right;2065 }2066 Variable sv;2067 if (isXmlPath(right) || isXmlPathFunction(right)) {2068 sv = evalXmlPathOnVariableByName(left, right);2069 } else {2070 sv = evalJsonPathOnVariableByName(left, right);2071 }2072 if (index != -1 && sv.isList()) {2073 List list = sv.getValue();2074 if (!list.isEmpty()) {2075 return new Variable(list.get(index));2076 }2077 }2078 return sv;2079 } else if (isJson(text)) {2080 Json json = Json.of(text);2081 return evalEmbeddedExpressions(new Variable(json.value()));2082 } else if (isXml(text)) {2083 Document doc = XmlUtils.toXmlDoc(text);2084 return evalEmbeddedExpressions(new Variable(doc));2085 } else if (isXmlPath(text)) {2086 return evalXmlPathOnVariableByName(RESPONSE, text);2087 } else {...

Full Screen

Full Screen

Source:ScriptBridge.java Github

copy

Full Screen

...98 }99 public String prettyXml(Object o) {100 ScriptValue sv = new ScriptValue(o);101 if (sv.isXml()) {102 Node node = sv.getValue(Node.class);103 return XmlUtils.toString(node, true);104 } else if (sv.isMapLike()) {105 Document doc = XmlUtils.fromMap(sv.getAsMap());106 return XmlUtils.toString(doc, true);107 } else {108 String xml = sv.getAsString();109 Document doc = XmlUtils.toXmlDoc(xml);110 return XmlUtils.toString(doc, true);111 }112 }113 public void set(String name, Object o) {114 context.vars.put(name, o);115 }116 public void setXml(String name, String xml) {117 context.vars.put(name, XmlUtils.toXmlDoc(xml));118 }119 // this makes sense mainly for xpath manipulation from within js120 public void set(String name, String path, Object value) {121 Script.setValueByPath(name, path, new ScriptValue(value), context);122 }123 // this makes sense mainly for xpath manipulation from within js124 public void setXml(String name, String path, String xml) {125 Script.setValueByPath(name, path, new ScriptValue(XmlUtils.toXmlDoc(xml)), context);126 }127 // set multiple variables in one shot128 public void set(Map<String, Object> map) {129 map.forEach((k, v) -> set(k, v));130 }131 // this makes sense for xml / xpath manipulation from within js132 public void remove(String name, String path) {133 Script.removeValueByPath(name, path, context);134 }135 public Object get(String exp) {136 ScriptValue sv;137 try {138 sv = Script.evalKarateExpression(exp, context); // even json path expressions will work139 } catch (Exception e) {140 context.logger.trace("karate.get failed for expression: '{}': {}", exp, e.getMessage());141 return null;142 }143 if (sv != null) {144 return sv.getAfterConvertingFromJsonOrXmlIfNeeded();145 } else {146 return null;147 }148 }149 public Object get(String exp, Object defaultValue) {150 Object result = get(exp);151 return result == null ? defaultValue : result;152 }153 public int sizeOf(Object o) {154 ScriptValue sv = new ScriptValue(o);155 if (sv.isMapLike()) {156 return sv.getAsMap().size();157 }158 if (sv.isListLike()) {159 return sv.getAsList().size();160 }161 return -1;162 }163 public List keysOf(Object o) {164 ScriptValue sv = new ScriptValue(o);165 if (sv.isMapLike()) {166 return new ArrayList(sv.getAsMap().keySet());167 }168 return null;169 }170 public List valuesOf(Object o) {171 ScriptValue sv = new ScriptValue(o);172 if (sv.isMapLike()) {173 return new ArrayList(sv.getAsMap().values());174 }175 if (sv.isListLike()) {176 return sv.getAsList();177 }178 return null;179 }180 public Map<String, Object> match(Object actual, Object expected) {181 AssertionResult ar = Script.matchNestedObject('.', "$", MatchType.EQUALS, actual, null, actual, expected, context);182 return ar.toMap();183 }184 public Map<String, Object> match(String exp) {185 MatchStep ms = new MatchStep(exp);186 AssertionResult ar = Script.matchNamed(ms.type, ms.name, ms.path, ms.expected, context);187 return ar.toMap();188 }189 public void forEach(Object o, ScriptObjectMirror som) {190 ScriptValue sv = new ScriptValue(o);191 if (!sv.isJsonLike()) {192 throw new RuntimeException("not map-like or list-like: " + o);193 }194 if (!som.isFunction()) {195 throw new RuntimeException("not a JS function: " + som);196 }197 if (sv.isListLike()) {198 List list = sv.getAsList();199 for (int i = 0; i < list.size(); i++) {200 som.call(som, list.get(i), i);201 }202 } else { //map203 Map map = sv.getAsMap();204 AtomicInteger i = new AtomicInteger();205 map.forEach((k, v) -> som.call(som, k, v, i.getAndIncrement()));206 }207 }208 public Object map(List list, ScriptObjectMirror som) {209 if (list == null) {210 return new ArrayList();211 }212 if (!som.isFunction()) {213 throw new RuntimeException("not a JS function: " + som);214 }215 List res = new ArrayList(list.size());216 for (int i = 0; i < list.size(); i++) {217 Object y = som.call(som, list.get(i), i);218 res.add(new ScriptValue(y).getValue()); // TODO graal219 }220 return res;221 }222 public Object filter(List list, ScriptObjectMirror som) {223 if (list == null) {224 return new ArrayList();225 }226 if (!som.isFunction()) {227 throw new RuntimeException("not a JS function: " + som);228 }229 List res = new ArrayList();230 for (int i = 0; i < list.size(); i++) {231 Object x = list.get(i);232 Object y = som.call(som, x, i);233 if (y instanceof Boolean) {234 if ((Boolean) y) {235 res.add(x);236 }237 } else if (y instanceof Number) { // support truthy numbers as a convenience238 String exp = y + " == 0";239 ScriptValue sv = Script.evalJsExpression(exp, null);240 if (!sv.isBooleanTrue()) {241 res.add(x);242 }243 }244 }245 return res;246 }247 public Object filterKeys(Object o, Map<String, Object> filter) {248 ScriptValue sv = new ScriptValue(o);249 if (!sv.isMapLike()) {250 return new LinkedHashMap();251 }252 Map map = sv.getAsMap();253 if (filter == null) {254 return map;255 }256 Map out = new LinkedHashMap(filter.size());257 filter.keySet().forEach(k -> {258 if (map.containsKey(k)) {259 out.put(k, map.get(k));260 }261 });262 return out;263 }264 public Object filterKeys(Object o, List keys) {265 return filterKeys(o, keys.toArray());266 }267 public Object filterKeys(Object o, Object... keys) {268 ScriptValue sv = new ScriptValue(o);269 if (!sv.isMapLike()) {270 return new LinkedHashMap();271 }272 Map map = sv.getAsMap();273 Map out = new LinkedHashMap(keys.length);274 for (Object key : keys) {275 if (map.containsKey(key)) {276 out.put(key, map.get(key));277 }278 }279 return out;280 }281 public Object repeat(int n, ScriptObjectMirror som) {282 if (!som.isFunction()) {283 throw new RuntimeException("not a JS function: " + som);284 }285 List res = new ArrayList();286 for (int i = 0; i < n; i++) {287 Object o = som.call(som, i);288 res.add(new ScriptValue(o).getValue()); // TODO graal289 }290 return res;291 }292 public Object mapWithKey(List list, String key) {293 if (list == null) {294 return new ArrayList();295 }296 List res = new ArrayList(list.size());297 for (Object o : list) {298 Map map = new LinkedHashMap();299 map.put(key, o);300 res.add(map);301 }302 return res;303 }304 public Object merge(Map... maps) {305 Map out = new LinkedHashMap();306 if (maps == null) {307 return out;308 }309 for (Map map : maps) {310 if (map == null) {311 continue;312 }313 out.putAll(map);314 }315 return out;316 }317 public Object append(Object... items) {318 List out = new ArrayList();319 if (items == null) {320 return out;321 }322 for (Object item : items) {323 if (item == null) {324 continue;325 }326 if (item instanceof ScriptObjectMirror) { // no need when graal327 ScriptObjectMirror som = (ScriptObjectMirror) item;328 if (som.isArray()) {329 out.addAll(som.values());330 } else {331 out.add(som);332 }333 } else if (item instanceof Collection) {334 out.addAll((Collection) item);335 } else {336 out.add(item);337 }338 }339 return out;340 }341 public List appendTo(String name, Object... values) {342 ScriptValue sv = context.vars.get(name);343 if (sv == null || !sv.isListLike()) {344 return Collections.EMPTY_LIST;345 }346 List list = appendTo(sv.getAsList(), values);347 context.vars.put(name, list);348 return list;349 }350 public List appendTo(List list, Object... values) {351 for (Object o : values) {352 if (o instanceof Collection) {353 list.addAll((Collection) o);354 } else {355 list.add(o);356 }357 }358 return list;359 }360 public Object jsonPath(Object o, String exp) {361 DocumentContext doc;362 if (o instanceof DocumentContext) {363 doc = (DocumentContext) o;364 } else {365 doc = JsonPath.parse(o);366 }367 return doc.read(exp);368 }369 public Object lowerCase(Object o) {370 ScriptValue sv = new ScriptValue(o);371 return sv.toLowerCase();372 }373 public Object xmlPath(Object o, String path) {374 if (!(o instanceof Node)) {375 if (o instanceof Map) {376 o = XmlUtils.fromMap((Map) o);377 } else {378 throw new RuntimeException("not XML or cannot convert: " + o);379 }380 }381 ScriptValue sv = Script.evalXmlPathOnXmlNode((Node) o, path);382 return sv.getValue();383 }384 public Object toBean(Object o, String className) {385 ScriptValue sv = new ScriptValue(o);386 DocumentContext doc = Script.toJsonDoc(sv, context);387 return JsonUtils.fromJson(doc.jsonString(), className);388 }389 public Object toMap(Object o) {390 if (o instanceof Map) {391 Map<String, Object> src = (Map) o;392 return new LinkedHashMap(src);393 }394 return o;395 }396 public Object toList(Object o) {397 if (o instanceof List) {398 List src = (List) o;399 return new ArrayList(src);400 }401 return o;402 }403 public Object toJson(Object o) {404 return toJson(o, false);405 }406 public Object toJson(Object o, boolean removeNulls) {407 Object result = JsonUtils.toJsonDoc(o).read("$");408 if (removeNulls) {409 JsonUtils.removeKeysWithNullValues(result);410 }411 return result;412 }413 public String toCsv(Object o) {414 ScriptValue sv = new ScriptValue(o);415 if (!sv.isListLike()) {416 throw new RuntimeException("not a list-like value:" + sv);417 }418 List<Map<String, Object>> list = sv.getAsList();419 return JsonUtils.toCsv(list);420 }421 public Object call(String fileName) {422 return call(false, fileName, null);423 }424 public Object call(String fileName, Object arg) {425 return call(false, fileName, arg);426 }427 public Object call(boolean sharedScope, String fileName) {428 return call(sharedScope, fileName, null);429 }430 // note that the implementation is subtly different from context.call()431 // because we are within a JS block432 public Object call(boolean sharedScope, String fileName, Object arg) {433 ScriptValue called = FileUtils.readFile(fileName, context);434 ScriptValue result;435 switch (called.getType()) {436 case FEATURE:437 Feature feature = called.getValue(Feature.class);438 // last param is for edge case where this.context is from function 439 // inited before call hierarchy was determined, see CallContext440 result = Script.evalFeatureCall(feature, arg, context, sharedScope);441 break;442 case JS_FUNCTION:443 ScriptObjectMirror som = called.getValue(ScriptObjectMirror.class);444 result = Script.evalJsFunctionCall(som, arg, context);445 break;446 default: // TODO remove ?447 context.logger.warn("not a js function or feature file: {} - {}", fileName, called);448 return null;449 }450 // if shared scope, a called feature would update the context directly451 if (sharedScope && !called.isFeature() && result.isMapLike()) {452 result.getAsMap().forEach((k, v) -> context.vars.put(k, v));453 }454 return result.getValue();455 }456 public Object callSingle(String fileName) {457 return callSingle(fileName, null);458 }459 public Object callSingle(String fileName, Object arg) {460 if (GLOBALS.containsKey(fileName)) {461 context.logger.trace("callSingle cache hit: {}", fileName);462 return GLOBALS.get(fileName);463 }464 long startTime = System.currentTimeMillis();465 context.logger.trace("callSingle waiting for lock: {}", fileName);466 synchronized (GLOBALS_LOCK) { // lock467 if (GLOBALS.containsKey(fileName)) { // retry468 long endTime = System.currentTimeMillis() - startTime;469 context.logger.warn("this thread waited {} milliseconds for callSingle lock: {}", endTime, fileName);470 return GLOBALS.get(fileName);471 }472 // this thread is the 'winner'473 context.logger.info(">> lock acquired, begin callSingle: {}", fileName);474 int minutes = context.getConfig().getCallSingleCacheMinutes();475 Object result = null;476 File cacheFile = null;477 if (minutes > 0) {478 String qualifiedFileName = FileUtils.toPackageQualifiedName(fileName);479 String cacheFileName = context.getConfig().getCallSingleCacheDir() + File.separator + qualifiedFileName + ".txt";480 cacheFile = new File(cacheFileName);481 long since = System.currentTimeMillis() - minutes * 60 * 1000;482 if (cacheFile.exists()) {483 long lastModified = cacheFile.lastModified();484 if (lastModified > since) {485 String json = FileUtils.toString(cacheFile);486 result = JsonUtils.toJsonDoc(json);487 context.logger.info("callSingleCache hit: {}", cacheFile);488 } else {489 context.logger.info("callSingleCache stale, last modified {} - is before {} (minutes: {})", lastModified, since,490 minutes);491 }492 } else {493 context.logger.info("callSingleCache file does not exist, will create: {}", cacheFile);494 }495 }496 if (result == null) {497 result = call(fileName, arg);498 if (minutes > 0) { // cacheFile will be not null499 ScriptValue cacheValue = new ScriptValue(result);500 if (cacheValue.isJsonLike()) {501 String json = cacheValue.getAsString();502 FileUtils.writeToFile(cacheFile, json);503 context.logger.info("callSingleCache write: {}", cacheFile);504 } else {505 context.logger.warn("callSingleCache write failed, not json-like: {}", cacheValue);506 }507 }508 }509 GLOBALS.put(fileName, result);510 context.logger.info("<< lock released, cached callSingle: {}", fileName);511 return result;512 }513 }514 public HttpRequest getPrevRequest() {515 return context.getPrevRequest();516 }517 public Object eval(String exp) {518 ScriptValue sv = Script.evalJsExpression(exp, context);519 return sv.getValue();520 }521 public ScriptValue fromString(String exp) {522 try {523 return Script.evalKarateExpression(exp, context);524 } catch (Exception e) {525 return new ScriptValue(exp);526 }527 }528 public ScriptValue fromObject(Object o) {529 return new ScriptValue(o);530 }531 public List<String> getTags() {532 return context.tags;533 }534 public Map<String, List<String>> getTagValues() {535 return context.tagValues;536 }537 public Map<String, Object> getInfo() {538 return context.getScenarioInfo();539 }540 public Scenario getScenario() {541 return context.getScenario();542 }543 public void proceed() {544 proceed(null);545 }546 public void proceed(String requestUrlBase) {547 HttpRequestBuilder request = new HttpRequestBuilder();548 String urlBase = requestUrlBase == null ? getAsString(ScriptValueMap.VAR_REQUEST_URL_BASE) : requestUrlBase;549 String uri = getAsString(ScriptValueMap.VAR_REQUEST_URI);550 String url = uri == null ? urlBase : urlBase + uri;551 request.setUrl(url);552 request.setMethod(getAsString(ScriptValueMap.VAR_REQUEST_METHOD));553 request.setHeaders(getValue(ScriptValueMap.VAR_REQUEST_HEADERS).getValue(MultiValuedMap.class));554 request.removeHeaderIgnoreCase(HttpUtils.HEADER_CONTENT_LENGTH);555 request.setBody(getValue(ScriptValueMap.VAR_REQUEST));556 HttpResponse response = context.getHttpClient().invoke(request, context);557 context.setPrevResponse(response);558 context.updateResponseVars();559 }560 public void embed(Object o, String contentType) {561 ScriptValue sv = new ScriptValue(o);562 if (contentType == null) {563 contentType = HttpUtils.getContentType(sv);564 }565 context.embed(sv.getAsByteArray(), contentType);566 }567 public File write(Object o, String path) {568 ScriptValue sv = new ScriptValue(o);569 path = FileUtils.getBuildDir() + File.separator + path;570 File file = new File(path);571 FileUtils.writeToFile(file, sv.getAsByteArray());572 return file;573 }574 public WebSocketClient webSocket(String url) {575 return webSocket(url, null, null);576 }577 public WebSocketClient webSocket(String url, Function<String, Boolean> handler) {578 return webSocket(url, handler, null);579 }580 public WebSocketClient webSocket(String url, Function<String, Boolean> handler, Map<String, Object> map) {581 if (handler == null) {582 handler = t -> true; // auto signal for websocket tests583 }584 WebSocketOptions options = new WebSocketOptions(url, map);585 options.setTextHandler(handler);586 return context.webSocket(options);587 }588 public WebSocketClient webSocketBinary(String url) {589 return webSocketBinary(url, null, null);590 }591 public WebSocketClient webSocketBinary(String url, Function<byte[], Boolean> handler) {592 return webSocketBinary(url, handler, null);593 }594 public WebSocketClient webSocketBinary(String url, Function<byte[], Boolean> handler, Map<String, Object> map) {595 if (handler == null) {596 handler = t -> true; // auto signal for websocket tests597 }598 WebSocketOptions options = new WebSocketOptions(url, map);599 options.setBinaryHandler(handler);600 return context.webSocket(options);601 }602 public void signal(Object result) {603 context.signal(result);604 }605 public Object listen(long timeout, ScriptObjectMirror som) {606 if (!som.isFunction()) {607 throw new RuntimeException("not a JS function: " + som);608 }609 return context.listen(timeout, () -> Script.evalJsFunctionCall(som, null, context));610 }611 public Object listen(long timeout) {612 return context.listen(timeout, null);613 }614 private ScriptValue getValue(String name) {615 ScriptValue sv = context.vars.get(name);616 return sv == null ? ScriptValue.NULL : sv;617 }618 private String getAsString(String name) {619 return getValue(name).getAsString();620 }621 public boolean pathMatches(String path) {622 String uri = getAsString(ScriptValueMap.VAR_REQUEST_URI);623 Map<String, String> pathParams = HttpUtils.parseUriPattern(path, uri);624 set(ScriptBindings.PATH_PARAMS, pathParams);625 boolean matched = pathParams != null;626 List<Integer> pathMatchScores = null;627 if (matched) {628 pathMatchScores = HttpUtils.calculatePathMatchScore(path);629 }630 set(ScriptBindings.PATH_MATCH_SCORES, pathMatchScores);631 return matched;632 }633 public boolean methodIs(String... methods) {634 String actual = getAsString(ScriptValueMap.VAR_REQUEST_METHOD);635 boolean match = Arrays.stream(methods).anyMatch((m) -> actual.equalsIgnoreCase(m));636 boolean existingValue = (Boolean) get(ScriptBindings.METHOD_MATCH, Boolean.FALSE);637 set(ScriptBindings.METHOD_MATCH, match || existingValue);638 return match;639 }640 public Object paramValue(String name) {641 List<String> list = paramValues(name);642 if (list == null) {643 return null;644 }645 if (list.size() == 1) {646 return list.get(0);647 }648 return list;649 }650 private List<String> paramValues(String name) {651 Map<String, List<String>> params = (Map) getValue(ScriptValueMap.VAR_REQUEST_PARAMS).getValue();652 if (params == null) {653 return null;654 }655 return params.get(name);656 }657 public boolean paramExists(String name) {658 List<String> list = paramValues(name);659 return list != null && !list.isEmpty();660 }661 public boolean headerContains(String name, String test) {662 Map<String, List<String>> headers = (Map) getValue(ScriptValueMap.VAR_REQUEST_HEADERS).getValue();663 if (headers == null) {664 return false;665 }666 List<String> list = headers.get(name);667 if (list == null) {668 return false;669 }670 for (String s : list) {671 if (s != null && s.contains(test)) {672 int existingValue = (int) get(ScriptBindings.HEADERS_MATCH_SCORE, 0);673 set(ScriptBindings.HEADERS_MATCH_SCORE, existingValue + 1);674 return true;675 }676 }677 return false;678 }679 public boolean typeContains(String test) {680 return headerContains(HttpUtils.HEADER_CONTENT_TYPE, test);681 }682 public boolean acceptContains(String test) {683 return headerContains(HttpUtils.HEADER_ACCEPT, test);684 }685 public Object bodyPath(String path) {686 ScriptValue sv = context.vars.get(ScriptValueMap.VAR_REQUEST);687 if (sv == null || sv.isNull()) {688 return null;689 }690 if (path.startsWith("/")) {691 return xmlPath(sv.getValue(), path);692 } else {693 return jsonPath(sv.getValue(), path);694 }695 }696 public FeatureServer start(String mock) {697 return start(Collections.singletonMap("mock", mock));698 }699 public FeatureServer start(Map<String, Object> config) {700 List<String> mocks;701 if (config.get("mock") instanceof String) {702 mocks = Arrays.asList((String) config.get("mock"));703 } else {704 mocks = (List<String>) config.get("mock");705 }706 if (mocks == null || mocks.isEmpty()) {707 throw new RuntimeException("'mock' is missing: " + config);708 }709 List<Feature> features = new ArrayList<>();710 for (String mock : mocks) {711 ScriptValue mockSv = FileUtils.readFile(mock, context);712 if (!mockSv.isFeature()) {713 throw new RuntimeException("'mock' is not a feature file: " + config + ", " + mockSv);714 }715 Feature feature = mockSv.getValue(Feature.class);716 features.add(feature);717 }718 String certFile = (String) config.get("cert");719 String keyFile = (String) config.get("key");720 Boolean ssl = (Boolean) config.get("ssl");721 if (ssl == null) {722 ssl = false;723 }724 Integer port = (Integer) config.get("port");725 if (port == null) {726 port = 0;727 }728 Map<String, Object> arg = (Map) config.get("arg");729 if (certFile != null && keyFile != null) {...

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1import com.intuit.karate.KarateOptions;2import com.intuit.karate.junit5.Karate;3import org.junit.jupiter.api.BeforeAll;4import org.junit.jupiter.api.BeforeEach;5import org.junit.jupiter.api.Test;6import org.junit.jupiter.api.TestInstance;7import org.junit.jupiter.api.extension.ExtendWith;8import com.intuit.karate.core.ScenarioRuntime;9import com.intuit.karate.core.ScenarioContext;10import com.intuit.karate.core.FeatureRuntime;11import com.intuit.karate.core.FeatureContext;12import com.intuit.karate.core.Feature;13import com.intuit.karate.core.FeatureResult;14import com.intuit.karate.core.ScenarioResult;15import com.intuit.karate.core.Scenario;16import com.intuit.karate.core.Match;17import com.intuit.karate.core.Variable;18import com.intuit.karate.core.VariableScope;19import com.intuit.karate.core.VariableType;20import com.intuit.karate.core.VariableValue;21import com.intuit.karate.core.VariableValueMap;22import com.intuit.karate.core.VariableValueList;23import com.intuit.karate.core.VariableValueMap;24import com.intuit.karate.core.VariableValueMap;25import com.intuit.karate.core.VariableValueList;26import com.intuit.karate.core.VariableValueMap;27import com.intuit.karate.core.VariableValueMap;28import com.intuit.karate.core.VariableValueMap;29import com.intuit.karat

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1package demo;2import com.intuit.karate.Match;3import com.intuit.karate.ScriptValue;4import com.intuit.karate.core.ScenarioRuntime;5public class 4 {6 public static void main(String[] args) {7 Match match = new Match("abc", "abc", false, true, null);8 ScriptValue value = match.getValue(new ScenarioRuntime());9 System.out.println(value);10 }11}12package demo;13import com.intuit.karate.Match;14import com.intuit.karate.ScriptValue;15import com.intuit.karate.core.ScenarioRuntime;16public class 5 {17 public static void main(String[] args) {18 Match match = new Match("abc", "abc", false, false, null);19 ScriptValue value = match.getValue(new ScenarioRuntime());20 System.out.println(value);21 }22}23package demo;24import com.intuit.karate.Match;25import com.intuit.karate.ScriptValue;26import com.intuit.karate.core.ScenarioRuntime;27public class 6 {28 public static void main(String[] args) {29 Match match = new Match("abc", "abc", true, true, null);30 ScriptValue value = match.getValue(new ScenarioRuntime());31 System.out.println(value);32 }33}34package demo;35import com.intuit.karate.Match;36import com.intuit.karate.ScriptValue;37import com.intuit.karate.core.ScenarioRuntime;38public class 7 {39 public static void main(String[] args) {40 Match match = new Match("abc", "abc", true, false, null);41 ScriptValue value = match.getValue(new ScenarioRuntime());42 System.out.println(value);43 }44}45package demo;46import com.intuit.karate.Match;47import com.intuit.karate.ScriptValue;48import com.intuit.karate.core.ScenarioRuntime;

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1import com.intuit.karate.Match;2import com.intuit.karate.Json;3import java.util.Map;4import java.util.HashMap;5import java.util.List;6import java.util.ArrayList;7import java.util.Arrays;8import java.util.Iterator;9import java.util.Map.Entry;10public class 4 {11public static void main(String[] args) {12Match match = Json.of("{ \"foo\": { \"bar\": { \"baz\": \"quux\" } } }").match("{ foo: { bar: { baz: '#(baz)' } } }");13String baz = match.getValue("baz");14System.out.println(baz);15}16}17import com.intuit.karate.Match;18import com.intuit.karate.Json;19import java.util.Map;20import java.util.HashMap;21import java.util.List;22import java.util.ArrayList;23import java.util.Arrays;24import java.util.Iterator;25import java.util.Map.Entry;26public class 5 {27public static void main(String[] args) {28Match match = Json.of("{ \"foo\": { \"bar\": { \"baz\": \"quux\" } } }").match("{ foo: { bar: { baz: '#(baz)' } } }");29List baz = match.getValues("baz");30System.out.println(baz);31}32}33import com.intuit.karate.Match;34import com.intuit.karate.Json;35import java.util.Map;36import java.util.HashMap;37import java.util.List;38import java.util.ArrayList;39import java.util.Arrays;40import java.util.Iterator;41import java.util.Map.Entry;42public class 6 {43public static void main(String[] args) {44Match match = Json.of("{ \"foo\": { \"bar\": { \"baz\": \"quux\" } } }").match("{ foo: { bar: { baz: '#(baz)' } } }");45Map baz = match.getMap();46System.out.println(baz);47}48}49import com.intuit.karate.Match;50import com.intuit.karate.Json;51import java.util.Map;52import java.util.HashMap;53import java.util.List;54import java.util.ArrayList;55import java.util.Arrays;56import java.util

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1package demo;2import com.intuit.karate.Match;3public class 4 {4public static void main(String[] args) {5Match m = Match.of("name", "John", "age", 35);6String name = m.getValue("name");7System.out.println(name);8}9}10package demo;11import com.intuit.karate.Match;12public class 5 {13public static void main(String[] args) {14Match m = Match.of("name", "John", "age", 35);15int age = m.getValue("age");16System.out.println(age);17}18}19package demo;20import com.intuit.karate.Match;21public class 6 {22public static void main(String[] args) {23Match m = Match.of("name", "John", "age", 35);24String name = m.getValues("name").get(0);25System.out.println(name);26}27}28package demo;29import com.intuit.karate.Match;30public class 7 {31public static void main(String[] args) {32Match m = Match.of("name", "John", "age", 35);33int age = m.getValues("age").get(0);34System.out.println(age);35}36}37package demo;38import com.intuit.karate.Match;39public class 8 {40public static void main(String[] args) {41Match m = Match.of("name", "John", "age", 35);42String name = m.getValues("name").get(0);43int age = m.getValues("age").get(0);44System.out.println(name);45System.out.println(age);46}47}48package demo;49import com.intuit.karate.Match;50public class 9 {51public static void main(String[] args) {52Match m = Match.of("name", "John", "age

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1import com.intuit.karate.Match2def match = Match.of('{"a":1,"b":2,"c":3}')3def a = match.getValue('a')4def b = match.getValue('b')5def c = match.getValue('c')6import com.intuit.karate.Match7def match = Match.of('{"a":1,"b":2,"c":3}')8def a = match.getValue('a')9def b = match.getValue('b')10def c = match.getValue('c')11import com.intuit.karate.Match12def match = Match.of('{"a":1,"b":2,"c":3}')13def a = match.getValue('a')14def b = match.getValue('b')15def c = match.getValue('c')16import com.intuit.karate.Match17def match = Match.of('{"a":1,"b":2,"c":3}')18def a = match.getValue('a')19def b = match.getValue('b')20def c = match.getValue('c')21import com.intuit.karate.Match22def match = Match.of('{"a":1,"b":2,"c":3}')23def a = match.getValue('a')24def b = match.getValue('b')25def c = match.getValue('c')26import com.intuit.karate.Match27def match = Match.of('{"a":1,"b":2,"c":3}')28def a = match.getValue('a')29def b = match.getValue('b

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1import com.intuit.karate.Match;2import com.intuit.karate.Match$;3public class 4 {4 public static void main(String[] args) {5 String s = "Hello, World";6 Match m = Match$.MODULE$.single("Hello, (.*)", s);7 System.out.println(m.getValue("world"));8 }9}10import com.intuit.karate.Match;11import com.intuit.karate.Match$;12public class 5 {13 public static void main(String[] args) {14 String s = "Hello, World";15 Match m = Match$.MODULE$.single("Hello, (.*)", s);16 System.out.println(m.getValue("world"));17 }18}19import com.intuit.karate.Match;20import com.intuit.karate.Match$;21public class 6 {22 public static void main(String[] args) {23 String s = "Hello, World";24 Match m = Match$.MODULE$.single("Hello, (.*)", s);25 System.out.println(m.getValue("world"));26 }27}28import com.intuit.karate.Match;29import com.intuit.karate.Match$;30public class 7 {31 public static void main(String[] args) {32 String s = "Hello, World";33 Match m = Match$.MODULE$.single("Hello, (.*)", s);34 System.out.println(m.getValue("world"));35 }36}37import com.intuit.karate.Match;38import com.intuit.karate.Match$;39public class 8 {40 public static void main(String[] args) {41 String s = "Hello, World";42 Match m = Match$.MODULE$.single("Hello, (.*)", s);

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1import com.intuit.karate.Match;2import com.intuit.karate.Json;3import java.util.Map;4import java.util.HashMap;5import java.util.List;6import java.util.ArrayList;7public class 4{8 public static void main(String[] args){9 String s = "{\n" +10 " \"dimensions\": {\n" +11 " }\n" +12 "}";13 Json json = Json.of(s);14 Map<String, Object> dimensions = json.getValue("dimensions");15 System.out.println(dimensions);16 }17}18{length=10, width=20, height=30}19import com.intuit.karate.Match;20import com.intuit.karate.Json;21import java.util.Map;22import java.util.HashMap;23import java.util.List;24import java.util.ArrayList;25public class 5{26 public static void main(String[] args){27 String s = "{\n" +28 " \"dimensions\": {\n" +29 " }\n" +30 "}";31 Json json = Json.of(s);32 List<Map<String, Object>> dimensions = json.getValues("dimensions");33 System.out.println(dimensions);34 }35}36[{length=10, width=20, height=30}]

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1package com.intuit.karate;2import org.junit.Test;3public class Test4 {4public void test() {5String json = "{\r6}";7Match m = Match.of(json);8String name = m.getValue("name");9System.out.println(name);10}11}

Full Screen

Full Screen

getValue

Using AI Code Generation

copy

Full Screen

1import com.intuit.karate.Match2import static org.junit.Assert.*3import org.junit.Test4{5 { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },6 { "name":"BMW", "models":[ "320", "X3", "X5" ] },7 { "name":"Fiat", "models":[ "500", "Panda" ] }8}9def match = Match.of(json)10def name = match.getValue('name')11def models = match.getValue('cars[1].models[0]')12import com.intuit.karate.Match13import static org.junit.Assert.*14import org.junit.Test15{16 { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },17 { "name":"BMW", "models":[ "320", "X3", "X5" ] },18 { "name":"Fiat", "models":[ "500", "Panda" ] }19}20def match = Match.of(json)21def name = match.getValue('name')22def models = match.getValue('cars[1].models[0]')23import com.intuit.karate.Match24import static org.junit.Assert.*25import org.junit.Test26{27 { "name":"Ford", "models":[ "Fiesta", "Focus", "Must

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful