How to use latest_template_raw method in localstack

Best Python code snippet using localstack_python

cloudformation_api.py

Source:cloudformation_api.py Github

copy

Full Screen

...161 return state162 def resource_status(self, resource_id: str):163 result = self._lookup(self.resource_states, resource_id)164 return result165 def latest_template_raw(self):166 if self.change_sets:167 return self.change_sets[-1]._template_raw168 return self._template_raw169 @property170 def resource_states(self):171 for resource_id in list(self._resource_states.keys()):172 self._set_resource_status_details(resource_id)173 return self._resource_states174 @property175 def stack_name(self):176 return self.metadata["StackName"]177 @property178 def stack_id(self):179 return self.metadata["StackId"]180 # TODO: potential performance issues due to many stack_parameters calls (cache or limit actual invocations)181 @property182 def resources(self): # TODO: not actually resources, split apart183 """Return dict of resources, parameters, conditions, and other stack metadata."""184 result = dict(self.template_resources)185 def add_params(defaults=True):186 for param in self.stack_parameters(defaults=defaults):187 if param["ParameterKey"] not in result:188 resolved_value = param.get("ResolvedValue")189 result[param["ParameterKey"]] = {190 "Type": "Parameter",191 "LogicalResourceId": param["ParameterKey"],192 "Properties": {193 "Value": (194 resolved_value195 if resolved_value is not None196 else param["ParameterValue"]197 )198 },199 }200 add_params(defaults=False)201 # TODO: conditions and mappings don't really belong here and should be handled separately202 for name, value in self.conditions.items():203 if name not in result:204 result[name] = {205 "Type": "Parameter",206 "LogicalResourceId": name,207 "Properties": {"Value": value},208 }209 for name, value in self.mappings.items():210 if name not in result:211 result[name] = {212 "Type": "Parameter",213 "LogicalResourceId": name,214 "Properties": {"Value": value},215 }216 add_params(defaults=True)217 return result218 @property219 def template_resources(self):220 return self.template.setdefault("Resources", {})221 @property222 def tags(self):223 return aws_responses.extract_tags(self.metadata)224 @property225 def imports(self):226 def _collect(o, **kwargs):227 if isinstance(o, dict):228 import_val = o.get("Fn::ImportValue")229 if import_val:230 result.add(import_val)231 return o232 result = set()233 recurse_object(self.resources, _collect)234 return result235 def outputs_list(self) -> List[Dict]:236 """Returns a copy of the outputs of this stack."""237 result = []238 for k, details in self.outputs.items():239 value = None240 try:241 template_deployer.resolve_refs_recursively(self, details)242 value = details["Value"]243 except Exception as e:244 LOG.debug("Unable to resolve references in stack outputs: %s - %s", details, e)245 exports = details.get("Export") or {}246 export = exports.get("Name")247 export = template_deployer.resolve_refs_recursively(self, export)248 description = details.get("Description")249 entry = {250 "OutputKey": k,251 "OutputValue": value,252 "Description": description,253 "ExportName": export,254 }255 result.append(entry)256 return result257 # TODO: check if metadata already populated/resolved and use it if possible (avoid unnecessary re-resolving)258 def stack_parameters(self, defaults=True) -> List[Dict[str, Any]]:259 result = {}260 # add default template parameter values261 if defaults:262 for key, value in self.template_parameters.items():263 param_value = value.get("Default")264 result[key] = {265 "ParameterKey": key,266 "ParameterValue": param_value,267 }268 # TODO: extract dynamic parameter resolving269 # TODO: support different types and refactor logic to use metadata (here not yet populated properly)270 param_type = value.get("Type", "")271 if not is_none_or_empty(param_type):272 if param_type == "AWS::SSM::Parameter::Value<String>":273 ssm_client = aws_stack.connect_to_service("ssm")274 resolved_value = ssm_client.get_parameter(Name=param_value)["Parameter"][275 "Value"276 ]277 result[key]["ResolvedValue"] = resolved_value278 elif param_type.startswith("AWS::"):279 LOG.info(280 f"Parameter Type '{param_type}' is currently not supported. Coming soon, stay tuned!"281 )282 else:283 # lets assume we support the normal CFn parameters284 pass285 # add stack parameters286 result.update({p["ParameterKey"]: p for p in self.metadata["Parameters"]})287 # add parameters of change sets288 for change_set in self.change_sets:289 result.update({p["ParameterKey"]: p for p in change_set.metadata["Parameters"]})290 result = list(result.values())291 return result292 @property293 def template_parameters(self):294 return self.template["Parameters"]295 @property296 def conditions(self):297 """Returns the (mutable) dict of stack conditions."""298 return self.template.setdefault("Conditions", {})299 @property300 def mappings(self):301 """Returns the (mutable) dict of stack mappings."""302 return self.template.setdefault("Mappings", {})303 @property304 def outputs(self):305 """Returns the (mutable) dict of stack outputs."""306 return self.template.setdefault("Outputs", {})307 @property308 def exports_map(self):309 result = {}310 for export in CloudFormationRegion.get().exports:311 result[export["Name"]] = export312 return result313 @property314 def nested_stacks(self):315 """Return a list of nested stacks that have been deployed by this stack."""316 result = [317 r for r in self.template_resources.values() if r["Type"] == "AWS::CloudFormation::Stack"318 ]319 result = [find_stack(r["Properties"].get("StackName")) for r in result]320 result = [r for r in result if r]321 return result322 @property323 def status(self):324 return self.metadata["StackStatus"]325 @property326 def resource_types(self):327 return [r.get("Type") for r in self.template_resources.values()]328 def resource(self, resource_id):329 return self._lookup(self.resources, resource_id)330 def _lookup(self, resource_map, resource_id):331 resource = resource_map.get(resource_id)332 if not resource:333 raise Exception(334 'Unable to find details for resource "%s" in stack "%s"'335 % (resource_id, self.stack_name)336 )337 return resource338 def copy(self):339 return Stack(metadata=dict(self.metadata), template=dict(self.template))340class StackChangeSet(Stack):341 def __init__(self, params=None, template=None):342 if template is None:343 template = {}344 if params is None:345 params = {}346 super(StackChangeSet, self).__init__(params, template)347 name = self.metadata["ChangeSetName"]348 if not self.metadata.get("ChangeSetId"):349 self.metadata["ChangeSetId"] = aws_stack.cf_change_set_arn(350 name, change_set_id=short_uid()351 )352 stack = self.stack = find_stack(self.metadata["StackName"])353 self.metadata["StackId"] = stack.stack_id354 self.metadata["Status"] = "CREATE_PENDING"355 @property356 def change_set_id(self):357 return self.metadata["ChangeSetId"]358 @property359 def change_set_name(self):360 return self.metadata["ChangeSetName"]361 @property362 def resources(self):363 result = dict(self.stack.resources)364 result.update(self.resources)365 return result366 @property367 def changes(self):368 result = self.metadata["Changes"] = self.metadata.get("Changes", [])369 return result370class CloudFormationRegion(RegionBackend):371 def __init__(self):372 # maps stack ID to stack details373 self.stacks: Dict[str, Stack] = {}374 # maps stack set ID to stack set details375 self.stack_sets: Dict[str, StackSet] = {}376 @property377 def exports(self):378 exports = []379 output_keys = {}380 for stack_id, stack in self.stacks.items():381 for output in stack.outputs_list():382 export_name = output.get("ExportName")383 if not export_name:384 continue385 if export_name in output_keys:386 # TODO: raise exception on stack creation in case of duplicate exports387 LOG.warning(388 "Found duplicate export name %s in stacks: %s %s",389 export_name,390 output_keys[export_name],391 stack.stack_id,392 )393 entry = {394 "ExportingStackId": stack.stack_id,395 "Name": export_name,396 "Value": output["OutputValue"],397 }398 exports.append(entry)399 output_keys[export_name] = stack.stack_id400 return exports401# --------------402# API ENDPOINTS403# --------------404def create_stack(req_params):405 state = CloudFormationRegion.get()406 template_deployer.prepare_template_body(req_params) # TODO: avoid mutating req_params directly407 template = template_preparer.parse_template(req_params["TemplateBody"])408 stack_name = template["StackName"] = req_params.get("StackName")409 stack = Stack(req_params, template)410 # find existing stack with same name, and remove it if this stack is in DELETED state411 existing = ([s for s in state.stacks.values() if s.stack_name == stack_name] or [None])[0]412 if existing:413 if "DELETE" not in existing.status:414 return error_response(415 'Stack named "%s" already exists with status "%s"' % (stack_name, existing.status),416 code=400,417 code_string="ValidationError",418 )419 state.stacks.pop(existing.stack_id)420 state.stacks[stack.stack_id] = stack421 LOG.debug(422 'Creating stack "%s" with %s resources ...', stack.stack_name, len(stack.template_resources)423 )424 deployer = template_deployer.TemplateDeployer(stack)425 try:426 # TODO: create separate step to first resolve parameters427 deployer.deploy_stack()428 except Exception as e:429 stack.set_stack_status("CREATE_FAILED")430 msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e)431 LOG.debug("%s %s", msg, traceback.format_exc())432 return error_response(msg, code=400, code_string="ValidationError")433 result = {"StackId": stack.stack_id}434 return result435def create_stack_set(req_params):436 state = CloudFormationRegion.get()437 stack_set = StackSet(req_params)438 stack_set_id = short_uid()439 stack_set.metadata["StackSetId"] = stack_set_id440 state.stack_sets[stack_set_id] = stack_set441 result = {"StackSetId": stack_set_id}442 return result443def create_stack_instances(req_params):444 state = CloudFormationRegion.get()445 set_name = req_params.get("StackSetName")446 stack_set = [sset for sset in state.stack_sets.values() if sset.stack_set_name == set_name]447 if not stack_set:448 return not_found_error('Stack set named "%s" does not exist' % set_name)449 stack_set = stack_set[0]450 op_id = req_params.get("OperationId") or short_uid()451 sset_meta = stack_set.metadata452 accounts = extract_url_encoded_param_list(req_params, "Accounts.member.%s")453 accounts = accounts or extract_url_encoded_param_list(454 req_params, "DeploymentTargets.Accounts.member.%s"455 )456 regions = extract_url_encoded_param_list(req_params, "Regions.member.%s")457 stacks_to_await = []458 for account in accounts:459 for region in regions:460 # deploy new stack461 LOG.debug('Deploying instance for stack set "%s" in region "%s"', set_name, region)462 cf_client = aws_stack.connect_to_service("cloudformation", region_name=region)463 kwargs = select_attributes(sset_meta, "TemplateBody") or select_attributes(464 sset_meta, "TemplateURL"465 )466 stack_name = "sset-%s-%s" % (set_name, account)467 result = cf_client.create_stack(StackName=stack_name, **kwargs)468 stacks_to_await.append((stack_name, region))469 # store stack instance470 instance = {471 "StackSetId": sset_meta["StackSetId"],472 "OperationId": op_id,473 "Account": account,474 "Region": region,475 "StackId": result["StackId"],476 "Status": "CURRENT",477 "StackInstanceStatus": {"DetailedStatus": "SUCCEEDED"},478 }479 instance = StackInstance(instance)480 stack_set.stack_instances.append(instance)481 # wait for completion of stack482 for stack in stacks_to_await:483 aws_stack.await_stack_completion(stack[0], region_name=stack[1])484 # record operation485 operation = {486 "OperationId": op_id,487 "StackSetId": stack_set.metadata["StackSetId"],488 "Action": "CREATE",489 "Status": "SUCCEEDED",490 }491 stack_set.operations[op_id] = operation492 result = {"OperationId": op_id}493 return result494def delete_stack(req_params):495 stack_name = req_params.get("StackName")496 stack = find_stack(stack_name)497 deployer = template_deployer.TemplateDeployer(stack)498 deployer.delete_stack()499 return {}500def delete_stack_set(req_params):501 state = CloudFormationRegion.get()502 set_name = req_params.get("StackSetName")503 stack_set = [sset for sset in state.stack_sets.values() if sset.stack_set_name == set_name]504 if not stack_set:505 return not_found_error('Stack set named "%s" does not exist' % set_name)506 for instance in stack_set[0].stack_instances:507 deployer = template_deployer.TemplateDeployer(instance.stack)508 deployer.delete_stack()509 return {}510def update_stack(req_params):511 stack_name = req_params.get("StackName")512 stack = find_stack(stack_name)513 if not stack:514 return not_found_error('Unable to update non-existing stack "%s"' % stack_name)515 template_preparer.prepare_template_body(req_params)516 template = template_preparer.parse_template(req_params["TemplateBody"])517 new_stack = Stack(req_params, template)518 deployer = template_deployer.TemplateDeployer(stack)519 try:520 deployer.update_stack(new_stack)521 except Exception as e:522 stack.set_stack_status("UPDATE_FAILED")523 msg = 'Unable to update stack "%s": %s' % (stack_name, e)524 LOG.debug("%s %s", msg, traceback.format_exc())525 return error_response(msg, code=400, code_string="ValidationError")526 result = {"StackId": stack.stack_id}527 return result528def update_stack_set(req_params):529 state = CloudFormationRegion.get()530 set_name = req_params.get("StackSetName")531 stack_set = [sset for sset in state.stack_sets.values() if sset.stack_set_name == set_name]532 if not stack_set:533 return not_found_error('Stack set named "%s" does not exist' % set_name)534 stack_set = stack_set[0]535 stack_set.metadata.update(req_params)536 op_id = req_params.get("OperationId") or short_uid()537 operation = {538 "OperationId": op_id,539 "StackSetId": stack_set.metadata["StackSetId"],540 "Action": "UPDATE",541 "Status": "SUCCEEDED",542 }543 stack_set.operations[op_id] = operation544 return {"OperationId": op_id}545def describe_stacks(req_params):546 state = CloudFormationRegion.get()547 stack_name = req_params.get("StackName")548 stack_list = list(state.stacks.values())549 stacks = [550 s.describe_details() for s in stack_list if stack_name in [None, s.stack_name, s.stack_id]551 ]552 if stack_name and not stacks:553 return error_response(554 "Stack with id %s does not exist" % stack_name,555 code=400,556 code_string="ValidationError",557 )558 result = {"Stacks": stacks}559 return result560def list_stacks(req_params):561 state = CloudFormationRegion.get()562 stack_status_filters = _get_status_filter_members(req_params)563 stacks = [564 s.describe_details()565 for s in state.stacks.values()566 if not stack_status_filters or s.status in stack_status_filters567 ]568 attrs = [569 "StackId",570 "StackName",571 "TemplateDescription",572 "CreationTime",573 "LastUpdatedTime",574 "DeletionTime",575 "StackStatus",576 "StackStatusReason",577 "ParentId",578 "RootId",579 "DriftInformation",580 ]581 stacks = [select_attributes(stack, attrs) for stack in stacks]582 result = {"StackSummaries": stacks}583 return result584def describe_stack_resource(req_params):585 stack_name = req_params.get("StackName")586 resource_id = req_params.get("LogicalResourceId")587 stack = find_stack(stack_name)588 if not stack:589 return stack_not_found_error(stack_name)590 details = stack.resource_status(resource_id)591 result = {"StackResourceDetail": details}592 return result593def describe_stack_resources(req_params):594 stack_name = req_params.get("StackName")595 resource_id = req_params.get("LogicalResourceId")596 phys_resource_id = req_params.get("PhysicalResourceId")597 if phys_resource_id and stack_name:598 return error_response("Cannot specify both StackName and PhysicalResourceId", code=400)599 # TODO: filter stack by PhysicalResourceId!600 stack = find_stack(stack_name)601 if not stack:602 return stack_not_found_error(stack_name)603 statuses = [604 res_status605 for res_id, res_status in stack.resource_states.items()606 if resource_id in [res_id, None]607 ]608 return {"StackResources": statuses}609def list_stack_resources(req_params):610 result = describe_stack_resources(req_params)611 if not isinstance(result, dict):612 return result613 result = {"StackResourceSummaries": result.pop("StackResources")}614 return result615def list_stack_instances(req_params):616 state = CloudFormationRegion.get()617 set_name = req_params.get("StackSetName")618 stack_set = [sset for sset in state.stack_sets.values() if sset.stack_set_name == set_name]619 if not stack_set:620 return not_found_error('Stack set named "%s" does not exist' % set_name)621 stack_set = stack_set[0]622 result = [inst.metadata for inst in stack_set.stack_instances]623 result = {"Summaries": result}624 return result625ChangeSetTypes = Literal["CREATE", "UPDATE", "IMPORT"]626def create_change_set(req_params: Dict[str, Any]):627 change_set_type: ChangeSetTypes = req_params.get("ChangeSetType", "UPDATE")628 stack_name: Optional[str] = req_params.get("StackName")629 change_set_name: Optional[str] = req_params.get("ChangeSetName")630 template_body: Optional[str] = req_params.get("TemplateBody")631 # s3 or secretsmanager url632 template_url: Optional[str] = req_params.get("TemplateUrl") or req_params.get("TemplateURL")633 if is_none_or_empty(change_set_name):634 return error_response(635 "ChangeSetName required", 400, "ValidationError"636 ) # TODO: check proper message637 if is_none_or_empty(stack_name):638 return error_response(639 "StackName required", 400, "ValidationError"640 ) # TODO: check proper message641 stack: Optional[Stack] = find_stack(stack_name)642 # validate and resolve template643 if template_body and template_url:644 return error_response(645 "Specify exactly one of 'TemplateBody' or 'TemplateUrl'", 400, "ValidationError"646 ) # TODO: check proper message647 if not template_body and not template_url:648 return error_response(649 "Specify exactly one of 'TemplateBody' or 'TemplateUrl'", 400, "ValidationError"650 ) # TODO: check proper message651 prepare_template_body(req_params) # TODO: function has too many unclear responsibilities652 template = template_preparer.parse_template(req_params["TemplateBody"])653 del req_params["TemplateBody"] # TODO: stop mutating req_params654 template["StackName"] = stack_name655 template[656 "ChangeSetName"657 ] = change_set_name # TODO: validate with AWS what this is actually doing?658 if change_set_type == "UPDATE":659 # add changeset to existing stack660 if stack is None:661 return error_response(662 f"Stack '{stack_name}' does not exist.", 400, "ValidationError"663 ) # stack should exist already664 elif change_set_type == "CREATE":665 # create new (empty) stack666 if stack is not None:667 return error_response(668 f"Stack {stack_name} already exists", 400, "ValidationError"669 ) # stack should not exist yet (TODO: check proper message)670 state = CloudFormationRegion.get()671 empty_stack_template = dict(template)672 empty_stack_template["Resources"] = {}673 req_params_copy = clone_stack_params(req_params)674 stack = Stack(req_params_copy, empty_stack_template)675 state.stacks[stack.stack_id] = stack676 stack.set_stack_status("REVIEW_IN_PROGRESS")677 elif change_set_type == "IMPORT":678 raise NotImplementedError() # TODO: implement importing resources679 else:680 msg = f"1 validation error detected: Value '{change_set_type}' at 'changeSetType' failed to satisfy constraint: Member must satisfy enum value set: [IMPORT, UPDATE, CREATE]"681 return error_response(msg, code=400, code_string="ValidationError")682 change_set = StackChangeSet(req_params, template)683 # TODO: refactor the flow here684 deployer = template_deployer.TemplateDeployer(change_set)685 deployer.construct_changes(686 stack,687 change_set,688 change_set_id=change_set.change_set_id,689 append_to_changeset=True,690 ) # TODO: ignores return value (?)691 deployer.apply_parameter_changes(change_set, change_set) # TODO: bandaid to populate metadata692 stack.change_sets.append(change_set)693 change_set.metadata[694 "Status"695 ] = "CREATE_COMPLETE" # technically for some time this should first be CREATE_PENDING696 change_set.metadata[697 "ExecutionStatus"698 ] = "AVAILABLE" # technically for some time this should first be UNAVAILABLE699 return {"StackId": change_set.stack_id, "Id": change_set.change_set_id}700def execute_change_set(req_params):701 stack_name = req_params.get("StackName")702 cs_name = req_params.get("ChangeSetName")703 change_set = find_change_set(cs_name, stack_name=stack_name)704 if not change_set:705 return not_found_error(706 'Unable to find change set "%s" for stack "%s"' % (cs_name, stack_name)707 )708 LOG.debug(709 'Executing change set "%s" for stack "%s" with %s resources ...',710 cs_name,711 stack_name,712 len(change_set.template_resources),713 )714 deployer = template_deployer.TemplateDeployer(change_set.stack)715 deployer.apply_change_set(change_set)716 change_set.stack.metadata["ChangeSetId"] = change_set.change_set_id717 return {}718def list_change_sets(req_params):719 stack_name = req_params.get("StackName")720 stack = find_stack(stack_name)721 if not stack:722 return not_found_error('Unable to find stack "%s"' % stack_name)723 result = [cs.metadata for cs in stack.change_sets]724 result = {"Summaries": result}725 return result726def list_stack_sets(req_params):727 state = CloudFormationRegion.get()728 result = [sset.metadata for sset in state.stack_sets.values()]729 result = {"Summaries": result}730 return result731def describe_change_set(req_params):732 stack_name = req_params.get("StackName")733 cs_name = req_params.get("ChangeSetName")734 change_set: Optional[StackChangeSet] = find_change_set(cs_name, stack_name=stack_name)735 if not change_set:736 return not_found_error(737 'Unable to find change set "%s" for stack "%s"' % (cs_name, stack_name)738 )739 return change_set.metadata740def describe_stack_set(req_params):741 state = CloudFormationRegion.get()742 set_name = req_params.get("StackSetName")743 result = [744 sset.metadata for sset in state.stack_sets.values() if sset.stack_set_name == set_name745 ]746 if not result:747 return not_found_error('Unable to find stack set "%s"' % set_name)748 result = {"StackSet": result[0]}749 return result750def describe_stack_set_operation(req_params):751 state = CloudFormationRegion.get()752 set_name = req_params.get("StackSetName")753 stack_set = [sset for sset in state.stack_sets.values() if sset.stack_set_name == set_name]754 if not stack_set:755 return not_found_error('Unable to find stack set "%s"' % set_name)756 stack_set = stack_set[0]757 op_id = req_params.get("OperationId")758 result = stack_set.operations.get(op_id)759 if not result:760 LOG.debug(761 'Unable to find operation ID "%s" for stack set "%s" in list: %s',762 op_id,763 set_name,764 list(stack_set.operations.keys()),765 )766 return not_found_error(767 'Unable to find operation ID "%s" for stack set "%s"' % (op_id, set_name)768 )769 result = {"StackSetOperation": result}770 return result771def list_exports(req_params):772 state = CloudFormationRegion.get()773 result = {"Exports": state.exports}774 return result775def list_imports(req_params):776 state = CloudFormationRegion.get()777 export_name = req_params.get("ExportName")778 importing_stack_names = []779 for stack in state.stacks.values():780 if export_name in stack.imports:781 importing_stack_names.append(stack.stack_name)782 result = {"Imports": importing_stack_names}783 return result784def validate_template(req_params):785 try:786 result = template_preparer.validate_template(req_params)787 result = "<tmp>%s</tmp>" % result788 result = xmltodict.parse(result)["tmp"]789 return result790 except Exception as err:791 return error_response("Template Validation Error: %s" % err)792def describe_stack_events(req_params):793 stack_name = req_params.get("StackName")794 state = CloudFormationRegion.get()795 events = []796 for stack_id, stack in state.stacks.items():797 if stack_name in [None, stack.stack_name, stack.stack_id]:798 events.extend(stack.events)799 return {"StackEvents": events}800def delete_change_set(req_params):801 stack_name = req_params.get("StackName")802 cs_name = req_params.get("ChangeSetName")803 change_set = find_change_set(cs_name, stack_name=stack_name)804 if not change_set:805 return not_found_error(806 'Unable to find change set "%s" for stack "%s"' % (cs_name, stack_name)807 )808 change_set.stack.change_sets = [809 cs for cs in change_set.stack.change_sets if cs.change_set_name != cs_name810 ]811 return {}812def get_template(req_params):813 stack_name = req_params.get("StackName")814 cs_name = req_params.get("ChangeSetName")815 stack = find_stack(stack_name)816 if cs_name:817 stack = find_change_set(stack_name=stack_name, cs_name=cs_name)818 if not stack:819 return stack_not_found_error(stack_name)820 result = {"TemplateBody": json.dumps(stack.latest_template_raw())}821 return result822def get_template_summary(req_params):823 stack_name = req_params.get("StackName")824 stack = None825 if stack_name:826 stack = find_stack(stack_name)827 if not stack:828 return stack_not_found_error(stack_name)829 else:830 template_deployer.prepare_template_body(req_params)831 template = template_preparer.parse_template(req_params["TemplateBody"])832 req_params["StackName"] = "tmp-stack"833 stack = Stack(req_params, template)834 result = stack.describe_details()...

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.

Run localstack automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful