Best Karate code snippet using com.intuit.karate.core.ScenarioEngine.recurseAndAttach
Source:ScenarioEngine.java
...941 Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap());942 runtime.magicVariables.forEach((k, v) -> {943 // even hidden variables may need pre-processing944 // for e.g. the __arg may contain functions that originated in a different js context945 Object o = recurseAndAttach(k, v, seen);946 JS.put(k, o == null ? v : o); // attach returns null if "not dirty"947 });948 vars.forEach((k, v) -> {949 // re-hydrate any functions from caller or background 950 Object o = recurseAndAttach(k, v.getValue(), seen);951 // note that we don't update the vars !952 // if we do, any "bad" out-of-context values will crash the constructor of Variable953 // it is possible the vars are detached / re-used later, so we kind of defer the inevitable954 JS.put(k, o == null ? v.getValue() : o); // attach returns null if "not dirty"955 });956 if (runtime.caller.arg != null && runtime.caller.arg.isMap()) {957 // add the call arg as separate "over ride" variables958 Map<String, Object> arg = runtime.caller.arg.getValue();959 recurseAndAttach("", arg, seen); // since arg is a map, it will not be cloned960 arg.forEach((k, v) -> {961 vars.put(k, new Variable(v));962 JS.put(k, v);963 });964 }965 JS.put(KARATE, bridge);966 JS.put(READ, readFunction); 967 // edge case: can be set by dynamic scenario outline background968 // or be left as-is because a callonce triggered init()969 if (requestBuilder == null) {970 HttpClient client = runtime.featureRuntime.suite.clientFactory.create(this);971 requestBuilder = new HttpRequestBuilder(client);972 }973 // TODO improve life cycle and concept of shared objects974 if (!runtime.caller.isNone()) {975 ScenarioEngine caller = runtime.caller.parentRuntime.engine;976 if (caller.driver != null) {977 setDriver(caller.driver);978 }979 if (caller.robot != null) {980 setRobot(caller.robot);981 }982 }983 }984 protected Map<String, Variable> detachVariables() {985 Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap());986 Map<String, Variable> detached = new HashMap(vars.size());987 vars.forEach((k, v) -> {988 Object o = recurseAndDetachAndShallowClone(k, v.getValue(), seen);989 detached.put(k, new Variable(o));990 });991 return detached;992 }993 // callSingle994 protected Object recurseAndAttachAndShallowClone(Object o) {995 return recurseAndAttachAndShallowClone(o, Collections.newSetFromMap(new IdentityHashMap()));996 }997 // callonce998 protected Object recurseAndAttachAndShallowClone(Object o, Set<Object> seen) {999 if (o instanceof List) {1000 o = new ArrayList((List) o);1001 } else if (o instanceof Map) {1002 o = new LinkedHashMap((Map) o);1003 }1004 Object result = recurseAndAttach("", o, seen);1005 return result == null ? o : result;1006 }1007 // call shared context1008 protected Object recurseAndAttach(Object o) {1009 Object result = recurseAndAttach("", o, Collections.newSetFromMap(new IdentityHashMap()));1010 return result == null ? o : result;1011 }1012 // call shared context1013 protected Object shallowClone(Object o) {1014 if (o instanceof List) {1015 return this.shallowCloneList((List<Object>) o);1016 } else if (o instanceof Map) {1017 return this.shallowCloneMap((Map<String, Object>) o);1018 } else {1019 return o;1020 }1021 }1022 // call shared context1023 protected List<Object> shallowCloneList(List<Object> o) {1024 List<Object> result = new ArrayList();1025 o.forEach(v -> {1026 if (v instanceof List) {1027 List copy = new ArrayList();1028 copy.addAll((List) v);1029 result.add(copy);1030 } else if (v instanceof Map) {1031 Map copy = new HashMap();1032 copy.putAll((Map) v);1033 result.add(copy);1034 } else {1035 result.add(v);1036 }1037 });1038 return result;1039 }1040 // call shared context1041 protected Map<String, Object> shallowCloneMap(Map<String, Object> o) {1042 Map<String, Object> result = new HashMap();1043 o.forEach((k, v) -> {1044 if (v instanceof List) {1045 List copy = new ArrayList();1046 copy.addAll((List) v);1047 result.put(k, copy);1048 } else if (v instanceof Map) {1049 Map copy = new HashMap();1050 copy.putAll((Map) v);1051 result.put(k, copy);1052 } else {1053 result.put(k, v);1054 }1055 });1056 return result;1057 }1058 private Object recurseAndAttach(String name, Object o, Set<Object> seen) {1059 if (o instanceof Value) {1060 Value value = Value.asValue(o);1061 try {1062 if (value.canExecute()) {1063 if (value.isMetaObject()) { // js function1064 return attach(value);1065 } else { // java function1066 return new JsExecutable(value);1067 }1068 } else { // anything else, including java-type references1069 return value;1070 }1071 } catch (Exception e) {1072 logger.warn("[*** attach ***] ignoring non-json value: '{}' - {}", name, e.getMessage());1073 // here we try our luck and hope that graal does not notice !1074 return value;1075 }1076 }1077 if (o instanceof Class) {1078 Class clazz = (Class) o;1079 Value value = JS.evalForValue("Java.type('" + clazz.getCanonicalName() + "')");1080 return value;1081 } else if (o instanceof JsFunction) {1082 JsFunction jf = (JsFunction) o;1083 try {1084 return attachSource(jf.source);1085 } catch (Exception e) {1086 logger.warn("[*** attach ***] ignoring js-function: '{}' - {}", name, e.getMessage());1087 return Value.asValue(null); // make sure we return a "dirty" value to force an update1088 }1089 } else if (o instanceof List) {1090 if (seen.add(o)) {1091 List list = (List) o;1092 int count = list.size();1093 try {1094 for (int i = 0; i < count; i++) {1095 Object child = list.get(i);1096 Object childResult = recurseAndAttach(name + "[" + i + "]", child, seen);1097 if (childResult != null) {1098 list.set(i, childResult);1099 }1100 }1101 } catch (Exception e) {1102 logger.warn("attach - immutable list: {}", name);1103 }1104 }1105 return null;1106 } else if (o instanceof Map) {1107 if (seen.add(o)) {1108 Map<String, Object> map = (Map) o;1109 try {1110 map.forEach((k, v) -> {1111 Object childResult = recurseAndAttach(name + "." + k, v, seen);1112 if (childResult != null) {1113 map.put(k, childResult);1114 }1115 });1116 } catch (Exception e) {1117 logger.warn("attach - immutable map: {}", name);1118 }1119 }1120 return null;1121 } else {1122 return null;1123 }1124 }1125 protected Object recurseAndDetachAndShallowClone(Object o) {1126 return recurseAndDetachAndShallowClone("", o, Collections.newSetFromMap(new IdentityHashMap()));1127 }1128 // callonce, callSingle and detachVariables()1129 private Object recurseAndDetachAndShallowClone(String name, Object o, Set<Object> seen) {1130 if (o instanceof List) {1131 o = new ArrayList((List) o);1132 } else if (o instanceof Map) {1133 o = new LinkedHashMap((Map) o);1134 }1135 Object result = recurseAndDetach(name, o, seen);1136 return result == null ? o : result;1137 }1138 private Object recurseAndDetach(String name, Object o, Set<Object> seen) {1139 if (o instanceof Value) {1140 Value value = (Value) o;1141 try {1142 if (value.canExecute()) {1143 if (value.isMetaObject()) { // js function1144 return new JsFunction(value);1145 } else { // java function 1146 return new JsExecutable(value);1147 }1148 } else if (value.isHostObject()) {1149 return value.asHostObject();1150 }1151 } catch (Exception e) {1152 logger.warn("[*** detach ***] ignoring non-json value: '{}' - {}", name, e.getMessage());1153 }1154 return null;1155 } else if (o instanceof List) {1156 List list = (List) o;1157 int count = list.size();1158 try {1159 for (int i = 0; i < count; i++) {1160 Object child = list.get(i);1161 Object childResult = recurseAndDetach(name + "[" + i + "]", child, seen);1162 if (childResult != null) {1163 list.set(i, childResult);1164 }1165 }1166 } catch (Exception e) {1167 logger.warn("detach - immutable list: {}", name);1168 }1169 return null;1170 } else if (o instanceof Map) {1171 if (seen.add(o)) {1172 Map<String, Object> map = (Map) o;1173 try {1174 map.forEach((k, v) -> {1175 Object childResult = recurseAndDetach(name + "." + k, v, seen);1176 if (childResult != null) {1177 map.put(k, childResult);1178 }1179 });1180 } catch (Exception e) {1181 logger.warn("detach - immutable map: {}", name);1182 }1183 }1184 return null;1185 } else {1186 return null;1187 }1188 }1189 public Value attachSource(CharSequence source) {1190 return JS.attachSource(source);1191 }1192 public Value attach(Value before) {1193 return JS.attach(before);1194 }1195 protected <T> Map<String, T> getOrEvalAsMap(Variable var, Object... args) {1196 if (var.isJsOrJavaFunction()) {1197 Variable res = executeFunction(var, args);1198 return res.isMap() ? res.getValue() : null;1199 } else {1200 return var.isMap() ? var.getValue() : null;1201 }1202 }1203 public Variable executeFunction(Variable var, Object... args) {1204 switch (var.type) {1205 case JS_FUNCTION:1206 Value jsFunction = var.getValue(); 1207 JsValue jsResult = executeJsValue(JS.attach(jsFunction), args);1208 return new Variable(jsResult);1209 case JAVA_FUNCTION: // definitely a "call" with a single argument1210 Function javaFunction = var.getValue();1211 Object arg = args.length == 0 ? null : args[0];1212 Object javaResult = javaFunction.apply(arg);1213 return new Variable(JsValue.unWrap(javaResult));1214 default:1215 throw new RuntimeException("expected function, but was: " + var);1216 }1217 }1218 private JsValue executeJsValue(Value function, Object... args) {1219 try {1220 return new JsValue(JsEngine.execute(function, args));1221 } catch (Exception e) {1222 String jsSource = function.getSourceLocation().getCharacters().toString();1223 KarateException ke = JsEngine.fromJsEvalException(jsSource, e, null);1224 setFailedReason(ke);1225 throw ke;1226 }1227 }1228 public Variable evalJs(String js) {1229 try {1230 return new Variable(JS.eval(js));1231 } catch (Exception e) {1232 KarateException ke = JsEngine.fromJsEvalException(js, e, null);1233 setFailedReason(ke);1234 throw ke;1235 }1236 }1237 public void setHiddenVariable(String key, Object value) {1238 if (value instanceof Variable) {1239 value = ((Variable) value).getValue();1240 }1241 JS.put(key, value);1242 }1243 public Object getVariable(String key) {1244 return JS.get(key).getValue();1245 }1246 public void setVariable(String key, Object value) {1247 Variable v;1248 Object o;1249 if (value instanceof Variable) {1250 v = (Variable) value;1251 o = v.getValue();1252 } else {1253 o = value;1254 try {1255 v = new Variable(value);1256 } catch (Exception e) {1257 v = null;1258 logger.warn("[*** set variable ***] ignoring non-json value: {} - {}", key, e.getMessage());1259 }1260 }1261 if (v != null) {1262 vars.put(key, v);1263 }1264 if (JS != null) {1265 JS.put(key, o);1266 }1267 }1268 public void setVariables(Map<String, Object> map) {1269 if (map == null) {1270 return;1271 }1272 map.forEach((k, v) -> setVariable(k, v));1273 }1274 public Map<String, Object> getAllVariablesAsMap() {1275 Map<String, Object> map = new HashMap(vars.size());1276 vars.forEach((k, v) -> map.put(k, v == null ? null : v.getValue()));1277 return map;1278 }1279 private static void validateVariableName(String name) {1280 if (!isValidVariableName(name)) {1281 throw new RuntimeException("invalid variable name: " + name);1282 }1283 if (KARATE.equals(name)) {1284 throw new RuntimeException("'karate' is a reserved name");1285 }1286 if (REQUEST.equals(name) || "url".equals(name)) {1287 throw new RuntimeException("'" + name + "' is a reserved name, also use the form '* " + name + " <expression>' instead");1288 }1289 }1290 private Variable evalAndCastTo(AssignType assignType, String exp, boolean docString) {1291 Variable v = docString ? new Variable(exp) : evalKarateExpression(exp);1292 switch (assignType) {1293 case BYTE_ARRAY:1294 return new Variable(v.getAsByteArray());1295 case STRING:1296 return new Variable(v.getAsString());1297 case XML:1298 return new Variable(v.getAsXml());1299 case XML_STRING:1300 String xml = XmlUtils.toString(v.getAsXml());1301 return new Variable(xml);1302 case JSON:1303 return new Variable(v.getValueAndForceParsingAsJson());1304 case YAML:1305 return new Variable(JsonUtils.fromYaml(v.getAsString()));1306 case CSV:1307 return new Variable(JsonUtils.fromCsv(v.getAsString()));1308 case COPY:1309 return v.copy(true);1310 default: // TEXT will be docstring, AUTO (def) will auto-parse JSON or XML1311 return v; // as is1312 }1313 }1314 public void assign(AssignType assignType, String name, String exp, boolean docString) {1315 name = StringUtils.trimToEmpty(name);1316 validateVariableName(name); // always validate when gherkin1317 if (vars.containsKey(name)) {1318 LOGGER.debug("over-writing existing variable '{}' with new value: {}", name, exp);1319 }1320 setVariable(name, evalAndCastTo(assignType, exp, docString));1321 }1322 private static boolean isEmbeddedExpression(String text) {1323 return text != null && (text.startsWith("#(") || text.startsWith("##(")) && text.endsWith(")");1324 }1325 private static class EmbedAction {1326 final boolean remove;1327 final Object value;1328 private EmbedAction(boolean remove, Object value) {1329 this.remove = remove;1330 this.value = value;1331 }1332 static EmbedAction remove() {1333 return new EmbedAction(true, null);1334 }1335 static EmbedAction update(Object value) {1336 return new EmbedAction(false, value);1337 }1338 }1339 public Variable evalEmbeddedExpressions(Variable value, boolean forMatch) {1340 switch (value.type) {1341 case STRING:1342 case MAP:1343 case LIST:1344 EmbedAction ea = recurseEmbeddedExpressions(value, forMatch);1345 if (ea != null) {1346 return ea.remove ? Variable.NULL : new Variable(ea.value);1347 } else {1348 return value;1349 }1350 case XML:1351 recurseXmlEmbeddedExpressions(value.getValue(), forMatch);1352 default:1353 return value;1354 }1355 }1356 private EmbedAction recurseEmbeddedExpressions(Variable node, boolean forMatch) {1357 switch (node.type) {1358 case LIST:1359 List list = node.getValue();1360 Set<Integer> indexesToRemove = new HashSet();1361 int count = list.size();1362 for (int i = 0; i < count; i++) {1363 EmbedAction ea = recurseEmbeddedExpressions(new Variable(list.get(i)), forMatch);1364 if (ea != null) {1365 if (ea.remove) {1366 indexesToRemove.add(i);1367 } else {1368 list.set(i, ea.value);1369 }1370 }1371 }1372 if (!indexesToRemove.isEmpty()) {1373 List copy = new ArrayList(count - indexesToRemove.size());1374 for (int i = 0; i < count; i++) {1375 if (!indexesToRemove.contains(i)) {1376 copy.add(list.get(i));1377 }1378 }1379 return EmbedAction.update(copy);1380 } else {1381 return null;1382 }1383 case MAP:1384 Map<String, Object> map = node.getValue();1385 List<String> keysToRemove = new ArrayList();1386 map.forEach((k, v) -> {1387 EmbedAction ea = recurseEmbeddedExpressions(new Variable(v), forMatch);1388 if (ea != null) {1389 if (ea.remove) {1390 keysToRemove.add(k);1391 } else {1392 map.put(k, ea.value);1393 }1394 }1395 });1396 for (String key : keysToRemove) {1397 map.remove(key);1398 }1399 return null;1400 case XML:1401 return null;1402 case STRING:1403 String value = StringUtils.trimToNull(node.getValue());1404 if (!isEmbeddedExpression(value)) {1405 return null;1406 }1407 boolean optional = value.charAt(1) == '#';1408 value = value.substring(optional ? 2 : 1);1409 try {1410 JsValue result = JS.eval(value);1411 if (optional) {1412 if (result.isNull()) {1413 return EmbedAction.remove();1414 }1415 if (forMatch && (result.isObject() || result.isArray())) {1416 // preserve optional JSON chunk schema-like references as-is, they are needed for future match attempts1417 return null;1418 }1419 }1420 return EmbedAction.update(result.getValue());1421 } catch (Exception e) {1422 logger.trace("embedded expression failed {}: {}", value, e.getMessage());1423 return null;1424 }1425 default:1426 // do nothing1427 return null;1428 }1429 }1430 private void recurseXmlEmbeddedExpressions(Node node, boolean forMatch) {1431 if (node.getNodeType() == Node.DOCUMENT_NODE) {1432 node = node.getFirstChild();1433 }1434 NamedNodeMap attribs = node.getAttributes();1435 int attribCount = attribs == null ? 0 : attribs.getLength();1436 Set<Attr> attributesToRemove = new HashSet(attribCount);1437 for (int i = 0; i < attribCount; i++) {1438 Attr attrib = (Attr) attribs.item(i);1439 String value = attrib.getValue();1440 value = StringUtils.trimToNull(value);1441 if (isEmbeddedExpression(value)) {1442 boolean optional = value.charAt(1) == '#';1443 value = value.substring(optional ? 2 : 1);1444 try {1445 JsValue jv = JS.eval(value);1446 if (optional && jv.isNull()) {1447 attributesToRemove.add(attrib);1448 } else {1449 attrib.setValue(jv.getAsString());1450 }1451 } catch (Exception e) {1452 logger.trace("xml-attribute embedded expression failed, {}: {}", attrib.getName(), e.getMessage());1453 }1454 }1455 }1456 for (Attr toRemove : attributesToRemove) {1457 attribs.removeNamedItem(toRemove.getName());1458 }1459 NodeList nodeList = node.getChildNodes();1460 int childCount = nodeList.getLength();1461 List<Node> nodes = new ArrayList(childCount);1462 for (int i = 0; i < childCount; i++) {1463 nodes.add(nodeList.item(i));1464 }1465 Set<Node> elementsToRemove = new HashSet(childCount);1466 for (Node child : nodes) {1467 String value = child.getNodeValue();1468 if (value != null) {1469 value = StringUtils.trimToEmpty(value);1470 if (isEmbeddedExpression(value)) {1471 boolean optional = value.charAt(1) == '#';1472 value = value.substring(optional ? 2 : 1);1473 try {1474 JsValue jv = JS.eval(value);1475 if (optional) {1476 if (jv.isNull()) {1477 elementsToRemove.add(child);1478 } else if (forMatch && (jv.isXml() || jv.isObject())) {1479 // preserve optional XML chunk schema-like references as-is, they are needed for future match attempts1480 } else {1481 child.setNodeValue(jv.getAsString());1482 }1483 } else {1484 if (jv.isXml() || jv.isObject()) {1485 Node evalNode = jv.isXml() ? jv.getValue() : XmlUtils.fromMap(jv.getValue());1486 if (evalNode.getNodeType() == Node.DOCUMENT_NODE) {1487 evalNode = evalNode.getFirstChild();1488 }1489 if (child.getNodeType() == Node.CDATA_SECTION_NODE) {1490 child.setNodeValue(XmlUtils.toString(evalNode));1491 } else {1492 evalNode = node.getOwnerDocument().importNode(evalNode, true);1493 child.getParentNode().replaceChild(evalNode, child);1494 }1495 } else {1496 child.setNodeValue(jv.getAsString());1497 }1498 }1499 } catch (Exception e) {1500 logger.trace("xml embedded expression failed, {}: {}", child.getNodeName(), e.getMessage());1501 }1502 }1503 } else if (child.hasChildNodes() || child.hasAttributes()) {1504 recurseXmlEmbeddedExpressions(child, forMatch);1505 }1506 }1507 for (Node toRemove : elementsToRemove) { // because of how the above routine works, these are always of type TEXT_NODE1508 Node parent = toRemove.getParentNode(); // element containing the text-node1509 Node grandParent = parent.getParentNode(); // parent element1510 grandParent.removeChild(parent);1511 }1512 }1513 public String replacePlaceholderText(String text, String token, String replaceWith) {1514 if (text == null) {1515 return null;1516 }1517 replaceWith = StringUtils.trimToNull(replaceWith);1518 if (replaceWith == null) {1519 return text;1520 }1521 try {1522 Variable v = evalKarateExpression(replaceWith);1523 replaceWith = v.getAsString();1524 } catch (Exception e) {1525 throw new RuntimeException("expression error (replace string values need to be within quotes): " + e.getMessage());1526 }1527 if (replaceWith == null) { // ignore if eval result is null1528 return text;1529 }1530 token = StringUtils.trimToNull(token);1531 if (token == null) {1532 return text;1533 }1534 char firstChar = token.charAt(0);1535 if (Character.isLetterOrDigit(firstChar)) {1536 token = '<' + token + '>';1537 }1538 return text.replace(token, replaceWith);1539 }1540 private static final String TOKEN = "token";1541 public void replaceTable(String text, List<Map<String, String>> list) {1542 if (text == null) {1543 return;1544 }1545 if (list == null) {1546 return;1547 }1548 for (Map<String, String> map : list) {1549 String token = map.get(TOKEN);1550 if (token == null) {1551 continue;1552 }1553 // the verbosity below is to be lenient with table second column name1554 List<String> keys = new ArrayList(map.keySet());1555 keys.remove(TOKEN);1556 Iterator<String> iterator = keys.iterator();1557 if (iterator.hasNext()) {1558 String key = keys.iterator().next();1559 String value = map.get(key);1560 replace(text, token, value);1561 }1562 }1563 }1564 public void set(String name, String path, Variable value) {1565 set(name, path, false, value, false, false);1566 }1567 private void set(String name, String path, String exp, boolean delete, boolean viaTable) {1568 set(name, path, isWithinParentheses(exp), evalKarateExpression(exp), delete, viaTable);1569 }1570 private void set(String name, String path, boolean isWithinParentheses, Variable value, boolean delete, boolean viaTable) {1571 name = StringUtils.trimToEmpty(name);1572 path = StringUtils.trimToNull(path);1573 if (viaTable && value.isNull() && !isWithinParentheses) {1574 // by default, skip any expression that evaluates to null unless the user expressed1575 // intent to over-ride by enclosing the expression in parentheses1576 return;1577 }1578 if (path == null) {1579 StringUtils.Pair nameAndPath = parseVariableAndPath(name);1580 name = nameAndPath.left;1581 path = nameAndPath.right;1582 }1583 Variable target = JS.bindings.hasMember(name) ? new Variable(JS.get(name)) : null; // should work in called features1584 if (isXmlPath(path)) {1585 if (target == null || target.isNull()) {1586 if (viaTable) { // auto create if using set via cucumber table as a convenience1587 Document empty = XmlUtils.newDocument();1588 target = new Variable(empty);1589 setVariable(name, target);1590 } else {1591 throw new RuntimeException("variable is null or not set '" + name + "'");1592 }1593 }1594 Document doc = target.getValue();1595 if (delete) {1596 XmlUtils.removeByPath(doc, path);1597 } else if (value.isXml()) {1598 Node node = value.getValue();1599 XmlUtils.setByPath(doc, path, node);1600 } else if (value.isMap()) { // cast to xml1601 Node node = XmlUtils.fromMap(value.getValue());1602 XmlUtils.setByPath(doc, path, node);1603 } else {1604 XmlUtils.setByPath(doc, path, value.getAsString());1605 }1606 setVariable(name, new Variable(doc));1607 } else { // assume json-path1608 if (target == null || target.isNull()) {1609 if (viaTable) { // auto create if using set via cucumber table as a convenience1610 Json json;1611 if (path.startsWith("$[") && !path.startsWith("$['")) {1612 json = Json.of("[]");1613 } else {1614 json = Json.of("{}");1615 }1616 target = new Variable(json.value());1617 setVariable(name, target);1618 } else {1619 throw new RuntimeException("variable is null or not set '" + name + "'");1620 }1621 }1622 Json json;1623 if (target.isMapOrList()) {1624 json = Json.of(target.<Object>getValue());1625 } else {1626 throw new RuntimeException("cannot set json path on type: " + target);1627 }1628 if (delete) {1629 json.remove(path);1630 } else {1631 json.set(path, value.<Object>getValue());1632 }1633 }1634 }1635 private static final String PATH = "path";1636 public void setViaTable(String name, String path, List<Map<String, String>> list) {1637 name = StringUtils.trimToEmpty(name);1638 path = StringUtils.trimToNull(path);1639 if (path == null) {1640 StringUtils.Pair nameAndPath = parseVariableAndPath(name);1641 name = nameAndPath.left;1642 path = nameAndPath.right;1643 }1644 for (Map<String, String> map : list) {1645 String append = (String) map.get(PATH);1646 if (append == null) {1647 continue;1648 }1649 List<String> keys = new ArrayList(map.keySet());1650 keys.remove(PATH);1651 int columnCount = keys.size();1652 for (int i = 0; i < columnCount; i++) {1653 String key = keys.get(i);1654 String expression = StringUtils.trimToNull(map.get(key));1655 if (expression == null) { // cucumber cell was left blank1656 continue; // skip1657 // default behavior is to skip nulls when the expression evaluates 1658 // this is driven by the routine in setValueByPath1659 // and users can over-ride this by simply enclosing the expression in parentheses1660 }1661 String suffix;1662 try {1663 int arrayIndex = Integer.valueOf(key);1664 suffix = "[" + arrayIndex + "]";1665 } catch (NumberFormatException e) { // default to the column position as the index1666 suffix = columnCount > 1 ? "[" + i + "]" : "";1667 }1668 String finalPath;1669 if (append.startsWith("/") || (path != null && path.startsWith("/"))) { // XML1670 if (path == null) {1671 finalPath = append + suffix;1672 } else {1673 finalPath = path + suffix + '/' + append;1674 }1675 } else {1676 if (path == null) {1677 path = "$";1678 }1679 finalPath = path + suffix + '.' + append;1680 }1681 set(name, finalPath, expression, false, true);1682 }1683 }1684 }1685 public static StringUtils.Pair parseVariableAndPath(String text) {1686 Matcher matcher = VAR_AND_PATH_PATTERN.matcher(text);1687 matcher.find();1688 String name = text.substring(0, matcher.end());1689 String path;1690 if (matcher.end() == text.length()) {1691 path = "";1692 } else {1693 path = text.substring(matcher.end()).trim();1694 }1695 if (isXmlPath(path) || isXmlPathFunction(path)) {1696 // xml, don't prefix for json1697 } else {1698 path = "$" + path;1699 }1700 return StringUtils.pair(name, path);1701 }1702 public Match.Result match(Match.Type matchType, String expression, String path, String rhs) {1703 String name = StringUtils.trimToEmpty(expression);1704 if (isDollarPrefixedJsonPath(name) || isXmlPath(name)) { // 1705 path = name;1706 name = RESPONSE;1707 }1708 if (name.startsWith("$")) { // in case someone used the dollar prefix by mistake on the LHS1709 name = name.substring(1);1710 }1711 path = StringUtils.trimToNull(path);1712 if (path == null) {1713 if (name.startsWith("(")) { // edge case, eval entire LHS1714 path = "$";1715 } else {1716 StringUtils.Pair pair = parseVariableAndPath(name);1717 name = pair.left;1718 path = pair.right;1719 }1720 }1721 if ("header".equals(name)) { // convenience shortcut for asserting against response header1722 return matchHeader(matchType, path, rhs);1723 }1724 Variable actual;1725 // karate started out by "defaulting" to JsonPath on the LHS of a match so we have this kludge1726 // but we now handle JS expressions of almost any shape on the LHS, if in doubt, wrap in parentheses1727 // actually it is not too bad - the XPath function check is the only odd one out1728 // rules:1729 // if not XPath function, wrapped in parentheses, involves function call1730 // [then] JS eval1731 // else if XPath, JsonPath, JsonPath wildcard ".." or "*" or "[?"1732 // [then] eval name, and do a JsonPath or XPath using the parsed path1733 if (isXmlPathFunction(path)1734 || (!name.startsWith("(") && !path.endsWith(")") && !path.contains(")."))1735 && (isDollarPrefixed(path) || isJsonPath(path) || isXmlPath(path))) {1736 actual = evalKarateExpression(name);1737 // edge case: java property getter, e.g. "driver.cookies"1738 if (!actual.isMap() && !actual.isList() && !isXmlPath(path) && !isXmlPathFunction(path)) {1739 actual = evalKarateExpression(expression); // fall back to JS eval of entire LHS1740 path = "$";1741 }1742 } else {1743 actual = evalKarateExpression(expression); // JS eval of entire LHS1744 path = "$";1745 }1746 if ("$".equals(path) || "/".equals(path)) {1747 // we have eval-ed the entire LHS, so proceed to match RHS to "$"1748 } else {1749 if (isDollarPrefixed(path)) { // json-path1750 actual = evalJsonPath(actual, path);1751 } else { // xpath1752 actual = evalXmlPath(actual, path);1753 }1754 }1755 Variable expected = evalKarateExpression(rhs, true);1756 return match(matchType, actual.getValue(), expected.getValue());1757 }1758 private Match.Result matchHeader(Match.Type matchType, String name, String exp) {1759 Variable expected = evalKarateExpression(exp, true);1760 String actual = response.getHeader(name);1761 return match(matchType, actual, expected.getValue());1762 }1763 public Match.Result match(Match.Type matchType, Object actual, Object expected) {1764 return Match.execute(JS, matchType, actual, expected);1765 }1766 private static final Pattern VAR_AND_PATH_PATTERN = Pattern.compile("\\w+");1767 private static final String VARIABLE_PATTERN_STRING = "[a-zA-Z][\\w]*";1768 private static final Pattern VARIABLE_PATTERN = Pattern.compile(VARIABLE_PATTERN_STRING);1769 private static final Pattern FUNCTION_PATTERN = Pattern.compile("^function[^(]*\\(");1770 private static final Pattern JS_PLACEHODER = Pattern.compile("\\$\\{.*?\\}");1771 public static boolean isJavaScriptFunction(String text) {1772 return FUNCTION_PATTERN.matcher(text).find();1773 }1774 public static boolean isValidVariableName(String name) {1775 return VARIABLE_PATTERN.matcher(name).matches();1776 }1777 public static boolean hasJavaScriptPlacehoder(String exp) {1778 return JS_PLACEHODER.matcher(exp).find();1779 }1780 public static final boolean isVariableAndSpaceAndPath(String text) {1781 return text.matches("^" + VARIABLE_PATTERN_STRING + "\\s+.+");1782 }1783 public static final boolean isVariable(String text) {1784 return VARIABLE_PATTERN.matcher(text).matches();1785 }1786 public static final boolean isWithinParentheses(String text) {1787 return text != null && text.startsWith("(") && text.endsWith(")");1788 }1789 public static final boolean isCallSyntax(String text) {1790 return text.startsWith("call ");1791 }1792 public static final boolean isCallOnceSyntax(String text) {1793 return text.startsWith("callonce ");1794 }1795 public static final boolean isGetSyntax(String text) {1796 return text.startsWith("get ") || text.startsWith("get[");1797 }1798 public static final boolean isJson(String text) {1799 return text.startsWith("{") || text.startsWith("[");1800 }1801 public static final boolean isXml(String text) {1802 return text.startsWith("<");1803 }1804 public static boolean isXmlPath(String text) {1805 return text.startsWith("/");1806 }1807 public static boolean isXmlPathFunction(String text) {1808 return text.matches("^[a-z-]+\\(.+");1809 }1810 public static final boolean isJsonPath(String text) {1811 return text.indexOf('*') != -1 || text.contains("..") || text.contains("[?");1812 }1813 public static final boolean isDollarPrefixed(String text) {1814 return text.startsWith("$");1815 }1816 public static final boolean isDollarPrefixedJsonPath(String text) {1817 return text.startsWith("$.") || text.startsWith("$[") || text.equals("$");1818 }1819 public static StringUtils.Pair parseCallArgs(String line) {1820 int pos = line.indexOf("read(");1821 if (pos != -1) {1822 pos = line.indexOf(')');1823 if (pos == -1) {1824 throw new RuntimeException("failed to parse call arguments: " + line);1825 }1826 return new StringUtils.Pair(line.substring(0, pos + 1), StringUtils.trimToNull(line.substring(pos + 1)));1827 }1828 pos = line.indexOf(' ');1829 if (pos == -1) {1830 return new StringUtils.Pair(line, null);1831 }1832 return new StringUtils.Pair(line.substring(0, pos), StringUtils.trimToNull(line.substring(pos)));1833 }1834 public Variable call(Variable called, Variable arg, boolean sharedScope) {1835 switch (called.type) {1836 case JS_FUNCTION:1837 case JAVA_FUNCTION:1838 return arg == null ? executeFunction(called) : executeFunction(called, new Object[]{arg.getValue()});1839 case FEATURE:1840 // will be always a map or a list of maps (loop call result) 1841 Object callResult = callFeature(called.getValue(), arg, -1, sharedScope);1842 this.rehydrateCallFeatureResult(callResult);1843 return new Variable(callResult);1844 default:1845 throw new RuntimeException("not a callable feature or js function: " + called);1846 }1847 }1848 private void rehydrateCallFeatureResult(Object callResult) {1849 Object callResultVariables = null;1850 if (callResult instanceof FeatureResult) {1851 callResultVariables = ((FeatureResult) callResult).getVariables();1852 ((FeatureResult) callResult).getConfig().detach();1853 } else if (callResult instanceof List) {1854 callResultVariables = new ArrayList<Map<String,Object>>();1855 final List<Map<String,Object>> finalCallResultVariables = (List<Map<String,Object>>)callResultVariables;1856 ((List<?>) callResult).forEach(result -> {1857 if (result instanceof FeatureResult) {1858 finalCallResultVariables.add(((FeatureResult) result).getVariables());1859 Config config = ((FeatureResult) result).getConfig();1860 config.detach();1861 }1862 });1863 callResultVariables = finalCallResultVariables;1864 } else {1865 callResultVariables = callResult;1866 }1867 Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap());1868 recurseAndAttach("", callResultVariables, seen);1869 }1870 public Variable getCallFeatureVariables(Variable featureResult) {1871 if (featureResult.getValue() instanceof FeatureResult) {1872 return new Variable(((FeatureResult) featureResult.getValue()).getVariables());1873 } else if (featureResult.isList()) {1874 List resultVariables = new ArrayList();1875 ((List) featureResult.getValue()).forEach(result -> {1876 if (result instanceof FeatureResult) {1877 resultVariables.add(this.getCallFeatureVariables(new Variable(result)).getValue());1878 } else {1879 resultVariables.add(result);1880 }1881 });1882 return new Variable(resultVariables);1883 } else {1884 return featureResult;1885 }1886 }1887 public Variable call(boolean callOnce, String exp, boolean sharedScope) {1888 StringUtils.Pair pair = parseCallArgs(exp);1889 Variable called = evalKarateExpression(pair.left);1890 Variable arg = pair.right == null ? null : evalKarateExpression(pair.right);1891 Variable result;1892 if (callOnce) {1893 result = callOnce(exp, called, arg, sharedScope);1894 } else {1895 result = call(called, arg, sharedScope);1896 }1897 Variable resultVariables = this.getCallFeatureVariables(result);1898 if (sharedScope) {1899 if (resultVariables.isMap()) {1900 setVariables(resultVariables.getValue());1901 } else if (resultVariables.isList()) {1902 ((List) resultVariables.getValue()).forEach(r -> {1903 setVariables((Map) r);1904 });1905 }1906 if (result.getValue() instanceof FeatureResult) {1907 setConfig(((FeatureResult) result.getValue()).getConfig());1908 }1909 }1910 return new Variable(resultVariables.getValue());1911 }1912 private Variable callOnceResult(ScenarioCall.Result result, boolean sharedScope) {1913 if (sharedScope) { // if shared scope1914 vars.clear(); // clean slate 1915 if (result.vars != null) {1916 Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap());1917 result.vars.forEach((k, v) -> {1918 // clone maps and lists so that subsequent steps don't modify data / references being passed around1919 Object o = recurseAndAttachAndShallowClone(v.getValue(), seen);1920 try {1921 vars.put(k, new Variable(o));1922 } catch (Exception e) {1923 logger.warn("[*** callonce result ***] ignoring non-json value: '{}' - {}", k, e.getMessage());1924 }1925 });1926 } else if (result.value != null) {1927 if (result.value.isMap()) {1928 ((Map) result.value.getValue()).forEach((k, v) -> {1929 try {1930 vars.put((String) k, new Variable(v));1931 } catch (Exception e) {1932 logger.warn("[*** callonce result ***] ignoring non-json value from result.value: '{}' - {}", k, e.getMessage());1933 }1934 });1935 } else {1936 logger.warn("[*** callonce result ***] ignoring non-map value from result.value: {}", result.value);1937 }1938 }1939 init(); // this will attach and also insert magic variables1940 // re-apply config from time of snapshot1941 // and note that setConfig() will attach functions such as configured "headers"1942 setConfig(new Config(result.config));1943 return Variable.NULL; // since we already reset the vars above we return null1944 // else the call() routine would try to do it again1945 // note that shared scope means a return value is meaningless1946 } else {1947 // deep-clone for the same reasons mentioned above1948 Object resultValue = recurseAndAttachAndShallowClone(result.value.getValue());1949 return new Variable(resultValue);1950 }1951 }1952 private Variable callOnce(String cacheKey, Variable called, Variable arg, boolean sharedScope) {1953 final Map<String, ScenarioCall.Result> CACHE;1954 if (runtime.perfMode) { // use suite-wide cache for gatling1955 CACHE = runtime.featureRuntime.suite.callOnceCache;1956 } else {1957 CACHE = runtime.featureRuntime.CALLONCE_CACHE;1958 }1959 ScenarioCall.Result result = CACHE.get(cacheKey);1960 if (result != null) {1961 logger.trace("callonce cache hit for: {}", cacheKey);1962 return callOnceResult(result, sharedScope);1963 }1964 long startTime = System.currentTimeMillis();1965 logger.trace("callonce waiting for lock: {}", cacheKey);1966 synchronized (CACHE) {1967 result = CACHE.get(cacheKey); // retry1968 if (result != null) {1969 long endTime = System.currentTimeMillis() - startTime;1970 logger.warn("this thread waited {} milliseconds for callonce lock: {}", endTime, cacheKey);1971 return callOnceResult(result, sharedScope);1972 }1973 // this thread is the 'winner'1974 logger.info(">> lock acquired, begin callonce: {}", cacheKey);1975 Variable resultValue = call(called, arg, sharedScope);1976 Variable resultVariables = this.getCallFeatureVariables(resultValue);1977 // we clone result (and config) here, to snapshot state at the point the callonce was invoked1978 // detaching is important (see JsFunction) so that we can keep the source-code aside1979 // and use it to re-create functions in a new JS context - and work around graal-js limitations1980 Map<String, Variable> clonedVars = called.isFeature() && sharedScope ? detachVariables() : null;1981 Config clonedConfig = new Config(config);1982 clonedConfig.detach();1983 Object resultObject = recurseAndDetachAndShallowClone(resultVariables.getValue());1984 result = new ScenarioCall.Result(new Variable(resultObject), clonedConfig, clonedVars);1985 CACHE.put(cacheKey, result);1986 logger.info("<< lock released, cached callonce: {}", cacheKey);1987 // another routine will apply globally if needed1988 // wrap and attach if being used immediately in a Scenario1989 return callOnceResult(result, sharedScope); 1990 }1991 }1992 public Object callFeature(Feature feature, Variable arg, int index, boolean sharedScope) {1993 if (arg == null || arg.isMap()) {1994 ScenarioCall call = new ScenarioCall(runtime, feature, arg);1995 call.setLoopIndex(index);1996 call.setSharedScope(sharedScope);1997 FeatureRuntime fr = new FeatureRuntime(call);1998 fr.run();1999 // VERY IMPORTANT ! switch back from called feature js context2000 THREAD_LOCAL.set(this);2001 FeatureResult result = fr.result;2002 runtime.addCallResult(result);2003 if (sharedScope) {2004 // if it's shared scope we don't want JS functions rehydrated in different contexts (threads)2005 // to polute parent scope/context2006 runtime.engine.recurseAndAttach(runtime.magicVariables);2007 runtime.engine.recurseAndAttach(runtime.engine.vars);2008 // todo: shared config2009 }2010 if (result.isFailed()) {2011 KarateException ke = result.getErrorMessagesCombined();2012 throw ke;2013 } else {2014 return result;2015 }2016 } else if (arg.isList() || arg.isJsOrJavaFunction()) {2017 List result = new ArrayList();2018 List<String> errors = new ArrayList();2019 int loopIndex = 0;2020 boolean isList = arg.isList();2021 Iterator iterator = isList ? arg.<List>getValue().iterator() : null;...
recurseAndAttach
Using AI Code Generation
1import com.intuit.karate.core.ScenarioEngine2import com.intuit.karate.core.Feature3import com.intuit.karate.core.FeatureContext4import com.intuit.karate.core.FeatureRuntime5import com.intuit.karate.core.FeatureRuntimeOptions6import com.intuit.karate.core.FeatureResult7import com.intuit.karate.core.FeatureWrapper8import com.intuit.karate.core.FeatureWrapper9import com.intuit.karate.core.Scenario10import com.intuit.karate.core.ScenarioContext11import com.intuit.karate.core.ScenarioResult12import com.intuit.karate.core.ScenarioWrapper13import com.intuit.karate.core.ScenarioWrapper14import com.intuit.karate.core.ScenarioEngine15import com.intuit.karate.core.Feature16import com.intuit.karate.core.FeatureContext17import com.intuit.karate.core.FeatureRuntime18import com.intuit.karate.core.FeatureRuntimeOptions19import com.intuit.karate.core.FeatureResult20import com.intuit.karate.core.FeatureWrapper21import com.intuit.karate.core.FeatureWrapper22import com.intuit.karate.core.Scenario23import com.intuit.karate.core.ScenarioContext24import com.intuit.karate.core.ScenarioResult25import com.intuit.karate.core.ScenarioWrapper26import com.intuit.karate.core.ScenarioWrapper27import com.intuit.karate.core.ScenarioEngine28import com.intuit.karate.core.Feature29import com.intuit.karate.core.FeatureContext30import com.intuit.karate.core.FeatureRuntime31import com.intuit.karate.core.FeatureRuntimeOptions32import com.intuit.karate.core.FeatureResult33import com.intuit.karate.core.FeatureWrapper34import com.intuit.karate.core.FeatureWrapper35import com.intuit.karate.core.Scenario36import com.intuit.karate.core.ScenarioContext37import com.intuit.karate.core.ScenarioResult38import com.intuit.karate.core.ScenarioWrapper39import com.intuit.karate.core.ScenarioWrapper40import com.intuit.karate.core.ScenarioEngine41import com.intuit.karate.core.Feature42import com.intuit.karate.core.FeatureContext43import com.intuit.karate.core.FeatureRuntime44import com.intuit.karate.core.FeatureRuntime
recurseAndAttach
Using AI Code Generation
1import com.intuit.karate.core.ScenarioEngine2import com.intuit.karate.core.Feature3import com.intuit.karate.core.FeatureContext4import com.intuit.karate.core.FeatureRuntime5import com.intuit.karate.core.Scenario6import com.intuit.karate.core.ScenarioContext7import com.intuit.karate.core.ScenarioRuntime8 * def engine = new ScenarioEngine()9 * def context = new ScenarioContext()10 * def runtime = new ScenarioRuntime(context)11 * def feature = new Feature()12 * def featureContext = new FeatureContext()13 * def featureRuntime = new FeatureRuntime(featureContext)14 * engine.recurseAndAttach(feature, featureRuntime, runtime)15 * def scenario = new Scenario()16 * def scenarioContext = new ScenarioContext()17 * def scenarioRuntime = new ScenarioRuntime(scenarioContext)18 * engine.recurseAndAttach(scenario, scenarioRuntime, runtime)19* def feature = karate.read('classpath:features/feature1.feature')20* karate.run(feature)21* def feature = karate.read('classpath:features/feature1.feature')22* karate.run(feature, { 'arg1': 'value1' })23* def feature = karate.read('classpath:features/feature1.feature')24* karate.run(feature, { 'arg1': 'value1' }, { 'arg2': 'value2' })25* def feature = karate.read('classpath:features/feature1.feature')26* karate.run(feature, { 'arg1': 'value1' }, { 'arg2': 'value2' }, { 'arg3': 'value3' })
recurseAndAttach
Using AI Code Generation
1import com.intuit.karate.core.ScenarioEngine2import com.intuit.karate.core.ScenarioRuntime3import com.intuit.karate.core.FeatureRuntime4import com.intuit.karate.core.FeatureRuntimeOptions5import com.intuit.karate.core.Feature6import com.intuit.karate.core.FeatureParser7import com.intuit.karate.core.FeatureResult8import com.intuit.karate.core.Scenario9import com.intuit.karate.core.ScenarioResult10import com.intuit.karate.core.ScenarioContext11import com.intuit.karate.core.ScenarioEngine12import com.intuit.karate.core.ScenarioRuntime13import com.intuit.karate.core.FeatureRuntime14import com.intuit.karate.core.FeatureRuntimeOptions15import com.intuit.karate.core.Feature16import com.intuit.karate.core.FeatureParser17import com.intuit.karate.core.FeatureResult18import com.intuit.karate.core.Scenario19import com.intuit.karate.core.ScenarioResult20import com.intuit.karate.core.ScenarioContext21import com.intuit.karate.core.ScenarioEngine22import com.intuit.karate.core.ScenarioRuntime23import com.intuit.karate.core.FeatureRuntime24import com.intuit.karate.core.FeatureRuntimeOptions25import com.intuit.karate.core.Feature26import com.intuit.karate.core.FeatureParser27import com.intuit.karate.core.FeatureResult28import com.intuit.karate.core.Scenario29import com.intuit.karate.core.ScenarioResult30import com.intuit.karate.core.ScenarioContext31import com.intuit.karate.core.ScenarioEngine32import com.intuit.karate.core.ScenarioRuntime33import com.intuit.karate.core.FeatureRuntime34import com.intuit.karate.core.FeatureRuntimeOptions35import com.intuit.karate.core.Feature36import com.intuit.karate.core.FeatureParser37import com.intuit.karate.core.FeatureResult38import com.intuit.karate.core.Scenario39import com.intuit.karate.core.ScenarioResult40import com.intuit.karate.core.ScenarioContext41import com.intuit.karate.core.ScenarioEngine42import com.intuit.karate.core.ScenarioRuntime43import com.intuit.karate
recurseAndAttach
Using AI Code Generation
1* def engine = karate.getScenarioEngine()2* def result = engine.recurseAndAttach(featureFile)3* def engine = karate.getScenarioEngine()4* def result = engine.recurseAndAttach(featureFile, [[a: 1, b: 2], [a: 3, b: 4]])5* def engine = karate.getScenarioEngine()6* def result = engine.recurseAndAttach(featureFile, [[a: 1, b: 2], [a: 3, b: 4]], { config -> config.foo = 'bar' })7* def engine = karate.getScenarioEngine()8* def result = engine.recurseAndAttach(featureFile, [[a: 1, b: 2], [a: 3, b: 4]], { config -> config.foo = 'bar' }, ['@tag1', '@tag2'])9* def engine = karate.getScenarioEngine()10* def result = engine.recurseAndAttach(featureFile, [[a: 1, b: 2], [a: 3, b: 4]], { config -> config.foo = 'bar' }, ['@tag1', '@tag2'], { vars -> vars.a = 'a'; vars.b = 'b' })11* def engine = karate.getScenarioEngine()
recurseAndAttach
Using AI Code Generation
1def engine = new com.intuit.karate.core.ScenarioEngine()2def features = new File('path/to/your/features')3def featureFiles = engine.findFeatureFiles(features)4def featureMap = engine.loadFeatureFiles(featureFiles)5def feature = featureMap.get('path/to/your/feature')6def scenario = feature.getScenario('scenario name')7def scenarioMap = scenario.getScenarioMap()8def recurseAndAttach = engine.getClass().getMethod('recurseAndAttach', Map.class, Map.class)9recurseAndAttach.invoke(engine, scenarioMap, featureMap)10def scenario = new com.intuit.karate.core.Scenario(scenarioMap)11def result = scenario.run()12def variables = result.getVariables()13* def myScenario = { “scenario name” : “Scenario to run”, “steps” : [ { “name” : “Given” }, { “name” : “When” } ] }14* def scenario = new com.intuit.karate.core.Scenario(myScenario)15* def result = scenario.run()16github.com/intuit/karate/blob/master/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java public class ScenarioEngine { ... } public class Scenario { ... }
recurseAndAttach
Using AI Code Generation
1import static com.intuit.karate.core.ScenarioEngine.recurseAndAttach2recurseAndAttach(path)3recurseAndAttach(path, false)4recurseAndAttach(path, false, ['pdf', 'png'])5recurseAndAttach(path, false, ['pdf', 'png'])6recurseAndAttach(path, false, ['pdf', 'png'])7recurseAndAttach(path, false, ['pdf', 'png'])8recurseAndAttach(path, false, ['pdf', 'png'])9recurseAndAttach(path, false, ['pdf', 'png'])10recurseAndAttach(path, false, ['pdf', 'png'])11recurseAndAttach(path, false, ['pdf', 'png'])12recurseAndAttach(path, false, ['pdf', 'png'])13recurseAndAttach(path, false, ['pdf', 'png'])14recurseAndAttach(path, false, ['pdf', 'png'])
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.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!