Best Python code snippet using autotest_python
resources.py
Source:resources.py
...19 def from_uri_args(cls, request, ag_name, **kwargs):20 return cls(request, models.AtomicGroup.objects.get(name=ag_name))21 def _uri_args(self):22 return {'ag_name': self.instance.name}23 def short_representation(self):24 rep = super(AtomicGroupClass, self).short_representation()25 rep['name'] = self.instance.name26 return rep27 def full_representation(self):28 rep = super(AtomicGroupClass, self).full_representation()29 rep.update({'max_number_of_machines':30 self.instance.max_number_of_machines,31 'labels':32 AtomicLabelTaggingCollection(fixed_entry=self).link()})33 return rep34 @classmethod35 def create_instance(cls, input_dict, containing_collection):36 cls._check_for_required_fields(input_dict, ('name',))37 return models.AtomicGroup.add_object(name=input_dict['name'])38 def update(self, input_dict):39 data = {'max_number_of_machines':40 input_dict.get('max_number_of_machines')}41 data = input_dict.remove_unspecified_fields(data)42 self.instance.update_object(**data)43class AtomicGroupClassCollection(resource_lib.Collection):44 queryset = models.AtomicGroup.valid_objects.all()45 entry_class = AtomicGroupClass46class Label(EntryWithInvalid):47 model = models.Label48 @classmethod49 def add_query_selectors(cls, query_processor):50 query_processor.add_field_selector('name')51 query_processor.add_field_selector(52 'is_platform', field='platform',53 value_transform=query_processor.read_boolean)54 @classmethod55 def from_uri_args(cls, request, label_name, **kwargs):56 return cls(request, models.Label.objects.get(name=label_name))57 def _uri_args(self):58 return {'label_name': self.instance.name}59 def short_representation(self):60 rep = super(Label, self).short_representation()61 rep.update({'name': self.instance.name,62 'is_platform': bool(self.instance.platform)})63 return rep64 def full_representation(self):65 rep = super(Label, self).full_representation()66 atomic_group_class = AtomicGroupClass.from_optional_instance(67 self._request, self.instance.atomic_group)68 rep.update({'atomic_group_class':69 atomic_group_class.short_representation(),70 'hosts': HostLabelingCollection(fixed_entry=self).link()})71 return rep72 @classmethod73 def create_instance(cls, input_dict, containing_collection):74 cls._check_for_required_fields(input_dict, ('name',))75 return models.Label.add_object(name=input_dict['name'])76 def update(self, input_dict):77 # TODO update atomic group78 if 'is_platform' in input_dict:79 self.instance.platform = input_dict['is_platform']80 self.instance.save()81class LabelCollection(resource_lib.Collection):82 queryset = models.Label.valid_objects.all()83 entry_class = Label84class AtomicLabelTagging(resource_lib.Relationship):85 related_classes = {'label': Label, 'atomic_group_class': AtomicGroupClass}86class AtomicLabelTaggingCollection(resource_lib.RelationshipCollection):87 entry_class = AtomicLabelTagging88class User(resource_lib.InstanceEntry):89 model = models.User90 _permitted_methods = ('GET,')91 @classmethod92 def from_uri_args(cls, request, username, **kwargs):93 if username == '@me':94 username = models.User.current_user().login95 return cls(request, models.User.objects.get(login=username))96 def _uri_args(self):97 return {'username': self.instance.login}98 def short_representation(self):99 rep = super(User, self).short_representation()100 rep['username'] = self.instance.login101 return rep102 def full_representation(self):103 rep = super(User, self).full_representation()104 accessible_hosts = HostCollection(self._request)105 accessible_hosts.set_query_parameters(accessible_by=self.instance.login)106 rep.update({'jobs': 'TODO',107 'recurring_runs': 'TODO',108 'acls':109 UserAclMembershipCollection(fixed_entry=self).link(),110 'accessible_hosts': accessible_hosts.link()})111 return rep112class UserCollection(resource_lib.Collection):113 _permitted_methods = ('GET',)114 queryset = models.User.objects.all()115 entry_class = User116class Acl(resource_lib.InstanceEntry):117 _permitted_methods = ('GET',)118 model = models.AclGroup119 @classmethod120 def from_uri_args(cls, request, acl_name, **kwargs):121 return cls(request, models.AclGroup.objects.get(name=acl_name))122 def _uri_args(self):123 return {'acl_name': self.instance.name}124 def short_representation(self):125 rep = super(Acl, self).short_representation()126 rep['name'] = self.instance.name127 return rep128 def full_representation(self):129 rep = super(Acl, self).full_representation()130 rep.update({'users':131 UserAclMembershipCollection(fixed_entry=self).link(),132 'hosts':133 HostAclMembershipCollection(fixed_entry=self).link()})134 return rep135 @classmethod136 def create_instance(cls, input_dict, containing_collection):137 cls._check_for_required_fields(input_dict, ('name',))138 return models.AclGroup.add_object(name=input_dict['name'])139 def update(self, input_dict):140 pass141class AclCollection(resource_lib.Collection):142 queryset = models.AclGroup.objects.all()143 entry_class = Acl144class UserAclMembership(resource_lib.Relationship):145 related_classes = {'user': User, 'acl': Acl}146 # TODO: check permissions147 # TODO: check for and add/remove "Everyone"148class UserAclMembershipCollection(resource_lib.RelationshipCollection):149 entry_class = UserAclMembership150class Host(EntryWithInvalid):151 model = models.Host152 @classmethod153 def add_query_selectors(cls, query_processor):154 query_processor.add_field_selector('hostname')155 query_processor.add_field_selector(156 'locked', value_transform=query_processor.read_boolean)157 query_processor.add_field_selector(158 'locked_by', field='locked_by__login',159 doc='Username of user who locked this host, if locked')160 query_processor.add_field_selector('status')161 query_processor.add_field_selector(162 'protection_level', field='protection',163 doc='Verify/repair protection level',164 value_transform=cls._read_protection)165 query_processor.add_field_selector(166 'accessible_by', field='aclgroup__users__login',167 doc='Username of user with access to this host')168 query_processor.add_related_existence_selector(169 'has_label', models.Label, 'name')170 @classmethod171 def _read_protection(cls, protection_input):172 return host_protections.Protection.get_value(protection_input)173 @classmethod174 def from_uri_args(cls, request, hostname, **kwargs):175 return cls(request, models.Host.objects.get(hostname=hostname))176 def _uri_args(self):177 return {'hostname': self.instance.hostname}178 def short_representation(self):179 rep = super(Host, self).short_representation()180 # TODO calling platform() over and over is inefficient181 platform_rep = (Label.from_optional_instance(self._request,182 self.instance.platform())183 .short_representation())184 rep.update({'hostname': self.instance.hostname,185 'locked': bool(self.instance.locked),186 'status': self.instance.status,187 'platform': platform_rep})188 return rep189 def full_representation(self):190 rep = super(Host, self).full_representation()191 protection = host_protections.Protection.get_string(192 self.instance.protection)193 locked_by = (User.from_optional_instance(self._request,194 self.instance.locked_by)195 .short_representation())196 labels = HostLabelingCollection(fixed_entry=self)197 acls = HostAclMembershipCollection(fixed_entry=self)198 queue_entries = QueueEntryCollection(self._request)199 queue_entries.set_query_parameters(host=self.instance.hostname)200 health_tasks = HealthTaskCollection(self._request)201 health_tasks.set_query_parameters(host=self.instance.hostname)202 rep.update({'locked_by': locked_by,203 'locked_on': self._format_datetime(self.instance.lock_time),204 'invalid': self.instance.invalid,205 'protection_level': protection,206 # TODO make these efficient207 'labels': labels.full_representation(),208 'acls': acls.full_representation(),209 'queue_entries': queue_entries.link(),210 'health_tasks': health_tasks.link()})211 return rep212 @classmethod213 def create_instance(cls, input_dict, containing_collection):214 cls._check_for_required_fields(input_dict, ('hostname',))215 # include locked here, rather than waiting for update(), to avoid race216 # conditions217 host = models.Host.add_object(hostname=input_dict['hostname'],218 locked=input_dict.get('locked', False))219 return host220 def update(self, input_dict):221 data = {'locked': input_dict.get('locked'),222 'protection': input_dict.get('protection_level')}223 data = input_dict.remove_unspecified_fields(data)224 if 'protection' in data:225 data['protection'] = self._read_protection(data['protection'])226 self.instance.update_object(**data)227 if 'platform' in input_dict:228 label = self.resolve_link(input_dict['platform']) .instance229 if not label.platform:230 raise exceptions.BadRequest('Label %s is not a platform' % label.name)231 for label in self.instance.labels.filter(platform=True):232 self.instance.labels.remove(label)233 self.instance.labels.add(label)234class HostCollection(resource_lib.Collection):235 queryset = models.Host.valid_objects.all()236 entry_class = Host237class HostLabeling(resource_lib.Relationship):238 related_classes = {'host': Host, 'label': Label}239class HostLabelingCollection(resource_lib.RelationshipCollection):240 entry_class = HostLabeling241class HostAclMembership(resource_lib.Relationship):242 related_classes = {'host': Host, 'acl': Acl}243 # TODO: acl.check_for_acl_violation_acl_group()244 # TODO: models.AclGroup.on_host_membership_change()245class HostAclMembershipCollection(resource_lib.RelationshipCollection):246 entry_class = HostAclMembership247class Test(resource_lib.InstanceEntry):248 model = models.Test249 @classmethod250 def add_query_selectors(cls, query_processor):251 query_processor.add_field_selector('name')252 @classmethod253 def from_uri_args(cls, request, test_name, **kwargs):254 return cls(request, models.Test.objects.get(name=test_name))255 def _uri_args(self):256 return {'test_name': self.instance.name}257 def short_representation(self):258 rep = super(Test, self).short_representation()259 rep['name'] = self.instance.name260 return rep261 def full_representation(self):262 rep = super(Test, self).full_representation()263 rep.update({'author': self.instance.author,264 'class': self.instance.test_class,265 'control_file_type':266 model_attributes.TestTypes.get_string(267 self.instance.test_type),268 'control_file_path': self.instance.path,269 'sync_count': self.instance.sync_count,270 'dependencies':271 TestDependencyCollection(fixed_entry=self).link(),272 })273 return rep274 @classmethod275 def create_instance(cls, input_dict, containing_collection):276 cls._check_for_required_fields(input_dict,277 ('name', 'control_file_type',278 'control_file_path'))279 test_type = model_attributes.TestTypes.get_value(280 input['control_file_type'])281 return models.Test.add_object(name=input_dict['name'],282 test_type=test_type,283 path=input_dict['control_file_path'])284 def update(self, input_dict):285 data = {'test_type': input_dict.get('control_file_type'),286 'path': input_dict.get('control_file_path'),287 'class': input_dict.get('class'),288 }289 data = input_dict.remove_unspecified_fields(data)290 self.instance.update_object(**data)291class TestCollection(resource_lib.Collection):292 queryset = models.Test.objects.all()293 entry_class = Test294class TestDependency(resource_lib.Relationship):295 related_classes = {'test': Test, 'label': Label}296class TestDependencyCollection(resource_lib.RelationshipCollection):297 entry_class = TestDependency298# TODO profilers299class ExecutionInfo(resource_lib.Resource):300 _permitted_methods = ('GET','POST')301 _job_fields = models.Job.get_field_dict()302 _DEFAULTS = {303 'control_file': '',304 'is_server': True,305 'dependencies': [],306 'machines_per_execution': 1,307 'run_verify': bool(_job_fields['run_verify'].default),308 'timeout_hrs': _job_fields['timeout'].default,309 'maximum_runtime_hrs': _job_fields['max_runtime_hrs'].default,310 'cleanup_before_job':311 model_attributes.RebootBefore.get_string(312 models.DEFAULT_REBOOT_BEFORE),313 'cleanup_after_job':314 model_attributes.RebootAfter.get_string(315 models.DEFAULT_REBOOT_AFTER),316 }317 def _query_parameters_accepted(self):318 return (('tests', 'Comma-separated list of test names to run'),319 ('kernels', 'TODO'),320 ('client_control_file',321 'Client control file segment to run after all specified '322 'tests'),323 ('profilers',324 'Comma-separated list of profilers to activate during the '325 'job'),326 ('use_container', 'TODO'),327 ('profile_only',328 'If true, run only profiled iterations; otherwise, always run '329 'at least one non-profiled iteration in addition to a '330 'profiled iteration'),331 ('upload_kernel_config',332 'If true, generate a server control file code that uploads '333 'the kernel config file to the client and tells the client of '334 'the new (local) path when compiling the kernel; the tests '335 'must be server side tests'))336 @classmethod337 def execution_info_from_job(cls, job):338 return {'control_file': job.control_file,339 'is_server': job.control_type == models.Job.ControlType.SERVER,340 'dependencies': [label.name for label341 in job.dependency_labels.all()],342 'machines_per_execution': job.synch_count,343 'run_verify': bool(job.run_verify),344 'timeout_hrs': job.timeout,345 'maximum_runtime_hrs': job.max_runtime_hrs,346 'cleanup_before_job':347 model_attributes.RebootBefore.get_string(job.reboot_before),348 'cleanup_after_job':349 model_attributes.RebootAfter.get_string(job.reboot_after),350 }351 def _get_execution_info(self, input_dict):352 tests = input_dict.get('tests', '')353 client_control_file = input_dict.get('client_control_file', None)354 if not tests and not client_control_file:355 return self._DEFAULTS356 test_list = tests.split(',')357 if 'profilers' in input_dict:358 profilers_list = input_dict['profilers'].split(',')359 else:360 profilers_list = []361 kernels = input_dict.get('kernels', '') # TODO362 if kernels:363 kernels = [dict(version=kernel) for kernel in kernels.split(',')]364 cf_info, test_objects, profiler_objects, label = (365 rpc_utils.prepare_generate_control_file(366 test_list, kernels, None, profilers_list))367 control_file_contents = control_file.generate_control(368 tests=test_objects, kernels=kernels,369 profilers=profiler_objects, is_server=cf_info['is_server'],370 client_control_file=client_control_file,371 profile_only=input_dict.get('profile_only', None),372 upload_kernel_config=input_dict.get(373 'upload_kernel_config', None))374 return dict(self._DEFAULTS,375 control_file=control_file_contents,376 is_server=cf_info['is_server'],377 dependencies=cf_info['dependencies'],378 machines_per_execution=cf_info['synch_count'])379 def handle_request(self):380 result = self.link()381 result['execution_info'] = self._get_execution_info(382 self._request.REQUEST)383 return self._basic_response(result)384class QueueEntriesRequest(resource_lib.Resource):385 _permitted_methods = ('GET',)386 def _query_parameters_accepted(self):387 return (('hosts', 'Comma-separated list of hostnames'),388 ('one_time_hosts',389 'Comma-separated list of hostnames not already in the '390 'Autotest system'),391 ('meta_hosts',392 'Comma-separated list of label names; for each one, an entry '393 'will be created and assigned at runtime to an available host '394 'with that label'),395 ('atomic_group_class', 'TODO'))396 def _read_list(self, list_string):397 if list_string:398 return list_string.split(',')399 return []400 def handle_request(self):401 request_dict = self._request.REQUEST402 hosts = self._read_list(request_dict.get('hosts'))403 one_time_hosts = self._read_list(request_dict.get('one_time_hosts'))404 meta_hosts = self._read_list(request_dict.get('meta_hosts'))405 atomic_group_class = request_dict.get('atomic_group_class')406 # TODO: bring in all the atomic groups magic from create_job()407 entries = []408 for hostname in one_time_hosts:409 models.Host.create_one_time_host(hostname)410 for hostname in hosts:411 entry = Host.from_uri_args(self._request, hostname)412 entries.append({'host': entry.link()})413 for label_name in meta_hosts:414 entry = Label.from_uri_args(self._request, label_name)415 entries.append({'meta_host': entry.link()})416 if atomic_group_class:417 entries.append({'atomic_group_class': atomic_group_class})418 result = self.link()419 result['queue_entries'] = entries420 return self._basic_response(result)421class Job(resource_lib.InstanceEntry):422 _permitted_methods = ('GET',)423 model = models.Job424 class _StatusConstraint(query_lib.Constraint):425 def apply_constraint(self, queryset, value, comparison_type,426 is_inverse):427 if comparison_type != 'equals' or is_inverse:428 raise query_lib.ConstraintError('Can only use this selector '429 'with equals')430 non_queued_statuses = [431 status for status, _432 in models.HostQueueEntry.Status.choices()433 if status != models.HostQueueEntry.Status.QUEUED]434 if value == 'queued':435 return queryset.exclude(436 hostqueueentry__status__in=non_queued_statuses)437 elif value == 'active':438 return queryset.filter(439 hostqueueentry__status__in=non_queued_statuses).filter(440 hostqueueentry__complete=False).distinct()441 elif value == 'complete':442 return queryset.exclude(hostqueueentry__complete=False)443 else:444 raise query_lib.ConstraintError('Value must be one of queued, '445 'active or complete')446 @classmethod447 def add_query_selectors(cls, query_processor):448 query_processor.add_field_selector('id')449 query_processor.add_field_selector('name')450 query_processor.add_selector(451 query_lib.Selector('status',452 doc='One of queued, active or complete'),453 Job._StatusConstraint())454 query_processor.add_keyval_selector('has_keyval', models.JobKeyval,455 'key', 'value')456 @classmethod457 def from_uri_args(cls, request, job_id, **kwargs):458 return cls(request, models.Job.objects.get(id=job_id))459 def _uri_args(self):460 return {'job_id': self.instance.id}461 @classmethod462 def _do_prepare_for_full_representation(cls, instances):463 models.Job.objects.populate_relationships(instances, models.JobKeyval,464 'keyvals')465 def short_representation(self):466 rep = super(Job, self).short_representation()467 rep.update({'id': self.instance.id,468 'owner': self.instance.owner,469 'name': self.instance.name,470 'priority':471 models.Job.Priority.get_string(self.instance.priority),472 'created_on':473 self._format_datetime(self.instance.created_on),474 })475 return rep476 def full_representation(self):477 rep = super(Job, self).full_representation()478 queue_entries = QueueEntryCollection(self._request)479 queue_entries.set_query_parameters(job=self.instance.id)480 drone_set = self.instance.drone_set and self.instance.drone_set.name481 rep.update({'email_list': self.instance.email_list,482 'parse_failed_repair':483 bool(self.instance.parse_failed_repair),484 'drone_set': drone_set,485 'execution_info':486 ExecutionInfo.execution_info_from_job(self.instance),487 'queue_entries': queue_entries.link(),488 'keyvals': dict((keyval.key, keyval.value)489 for keyval in self.instance.keyvals)490 })491 return rep492 @classmethod493 def create_instance(cls, input_dict, containing_collection):494 owner = input_dict.get('owner')495 if not owner:496 owner = models.User.current_user().login497 cls._check_for_required_fields(input_dict, ('name', 'execution_info',498 'queue_entries'))499 execution_info = input_dict['execution_info']500 cls._check_for_required_fields(execution_info, ('control_file',501 'is_server'))502 if execution_info['is_server']:503 control_type = models.Job.ControlType.SERVER504 else:505 control_type = models.Job.ControlType.CLIENT506 options = dict(507 name=input_dict['name'],508 priority=input_dict.get('priority', None),509 control_file=execution_info['control_file'],510 control_type=control_type,511 is_template=input_dict.get('is_template', None),512 timeout=execution_info.get('timeout_hrs'),513 max_runtime_hrs=execution_info.get('maximum_runtime_hrs'),514 synch_count=execution_info.get('machines_per_execution'),515 run_verify=execution_info.get('run_verify'),516 email_list=input_dict.get('email_list', None),517 dependencies=execution_info.get('dependencies', ()),518 reboot_before=execution_info.get('cleanup_before_job'),519 reboot_after=execution_info.get('cleanup_after_job'),520 parse_failed_repair=input_dict.get('parse_failed_repair', None),521 drone_set=input_dict.get('drone_set', None),522 keyvals=input_dict.get('keyvals', None))523 host_objects, metahost_label_objects, atomic_group = [], [], None524 for queue_entry in input_dict['queue_entries']:525 if 'host' in queue_entry:526 host = queue_entry['host']527 if host: # can be None, indicated a hostless job528 host_entry = containing_collection.resolve_link(host)529 host_objects.append(host_entry.instance)530 elif 'meta_host' in queue_entry:531 label_entry = containing_collection.resolve_link(532 queue_entry['meta_host'])533 metahost_label_objects.append(label_entry.instance)534 if 'atomic_group_class' in queue_entry:535 atomic_group_entry = containing_collection.resolve_link(536 queue_entry['atomic_group_class'])537 if atomic_group:538 assert atomic_group_entry.instance.id == atomic_group.id539 else:540 atomic_group = atomic_group_entry.instance541 job_id = rpc_utils.create_new_job(542 owner=owner,543 options=options,544 host_objects=host_objects,545 metahost_objects=metahost_label_objects,546 atomic_group=atomic_group)547 return models.Job.objects.get(id=job_id)548 def update(self, input_dict):549 # Required for POST, doesn't actually support PUT550 pass551class JobCollection(resource_lib.Collection):552 queryset = models.Job.objects.order_by('-id')553 entry_class = Job554class QueueEntry(resource_lib.InstanceEntry):555 _permitted_methods = ('GET', 'PUT')556 model = models.HostQueueEntry557 @classmethod558 def add_query_selectors(cls, query_processor):559 query_processor.add_field_selector('host', field='host__hostname')560 query_processor.add_field_selector('job', field='job__id')561 @classmethod562 def from_uri_args(cls, request, queue_entry_id):563 instance = models.HostQueueEntry.objects.get(id=queue_entry_id)564 return cls(request, instance)565 def _uri_args(self):566 return {'queue_entry_id': self.instance.id}567 def short_representation(self):568 rep = super(QueueEntry, self).short_representation()569 if self.instance.host:570 host = (Host(self._request, self.instance.host)571 .short_representation())572 else:573 host = None574 job = Job(self._request, self.instance.job)575 host = Host.from_optional_instance(self._request, self.instance.host)576 label = Label.from_optional_instance(self._request,577 self.instance.meta_host)578 atomic_group_class = AtomicGroupClass.from_optional_instance(579 self._request, self.instance.atomic_group)580 rep.update(581 {'job': job.short_representation(),582 'host': host.short_representation(),583 'label': label.short_representation(),584 'atomic_group_class':585 atomic_group_class.short_representation(),586 'status': self.instance.status,587 'execution_path': self.instance.execution_subdir,588 'started_on': self._format_datetime(self.instance.started_on),589 'aborted': bool(self.instance.aborted)})590 return rep591 def update(self, input_dict):592 if 'aborted' in input_dict:593 if input_dict['aborted'] != True:594 raise exceptions.BadRequest('"aborted" can only be set to true')595 query = models.HostQueueEntry.objects.filter(pk=self.instance.pk)596 models.AclGroup.check_abort_permissions(query)597 rpc_utils.check_abort_synchronous_jobs(query)598 self.instance.abort(thread_local.get_user())599class QueueEntryCollection(resource_lib.Collection):600 queryset = models.HostQueueEntry.objects.order_by('-id')601 entry_class = QueueEntry602class HealthTask(resource_lib.InstanceEntry):603 _permitted_methods = ('GET',)604 model = models.SpecialTask605 @classmethod606 def add_query_selectors(cls, query_processor):607 query_processor.add_field_selector('host', field='host__hostname')608 @classmethod609 def from_uri_args(cls, request, task_id):610 instance = models.SpecialTask.objects.get(id=task_id)611 return cls(request, instance)612 def _uri_args(self):613 return {'task_id': self.instance.id}614 def short_representation(self):615 rep = super(HealthTask, self).short_representation()616 host = Host(self._request, self.instance.host)617 queue_entry = QueueEntry.from_optional_instance(618 self._request, self.instance.queue_entry)619 rep.update(620 {'host': host.short_representation(),621 'task_type': self.instance.task,622 'started_on':623 self._format_datetime(self.instance.time_started),624 'status': self.instance.status,625 'queue_entry': queue_entry.short_representation()626 })627 return rep628 @classmethod629 def create_instance(cls, input_dict, containing_collection):630 cls._check_for_required_fields(input_dict, ('task_type',))631 host = containing_collection.base_entry.instance632 models.AclGroup.check_for_acl_violation_hosts((host,))633 return models.SpecialTask.schedule_special_task(host,634 input_dict['task_type'])635 def update(self, input_dict):636 # Required for POST, doesn't actually support PUT637 pass638class HealthTaskCollection(resource_lib.Collection):639 entry_class = HealthTask...
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!!