Best Python code snippet using slash
fixture.py
Source:fixture.py
1import datetime2from django.contrib.auth.models import Group3from django.contrib.contenttypes.models import ContentType4from django.utils import timezone5from pinax.eventlog.models import Log6import address.models as ad7from api.v1.tests.factories import GoalMetricFactory, TransactionFactory, PositionLotFactory, \8 InvestmentCycleObservationFactory9from client.models import Client, ClientAccount, IBAccount, RiskProfileAnswer, \10 RiskProfileGroup, RiskProfileQuestion11from main.constants import ACCOUNT_TYPE_PERSONAL12from main.event import Event13from main.models import Advisor, AssetClass, DailyPrice, Execution, \14 ExecutionDistribution, ExternalAsset, Firm, Goal, \15 GoalMetricGroup, GoalSetting, GoalType, HistoricalBalance, MarketIndex, \16 PortfolioSet, Region, Ticker, User, ExternalInstrument, \17 Transaction, MarketOrderRequest18from main.risk_profiler import MINIMUM_RISK19from retiresmartz.models import RetirementPlan20class Fixture1:21 @classmethod22 def portfolioset1(cls):23 params = {24 'name': 'portfolioset1',25 'risk_free_rate': 0.02,26 }27 return PortfolioSet.objects.get_or_create(id=1, defaults=params)[0]28 @classmethod29 def portfolioset2(cls):30 params = {31 'name': 'portfolioset2',32 'risk_free_rate': 0.02,33 }34 return PortfolioSet.objects.get_or_create(id=2, defaults=params)[0]35 @classmethod36 def firm1(cls):37 params = {38 'name': 'example_inc',39 'token': 'example_inc',40 'default_portfolio_set': Fixture1.portfolioset1(),41 }42 return Firm.objects.get_or_create(slug='example_inc', defaults=params)[0]43 @classmethod44 def user_group_advisors(cls):45 return Group.objects.get_or_create(name=User.GROUP_ADVISOR)[0]46 @classmethod47 def user_group_clients(cls):48 return Group.objects.get_or_create(name=User.GROUP_CLIENT)[0]49 @classmethod50 def advisor1_user(cls):51 params = {52 'first_name': "test",53 'last_name': "advisor",54 }55 i, c = User.objects.get_or_create(email="advisor@example.com", defaults=params)56 if c:57 i.groups.add(Fixture1.user_group_advisors())58 return i59 @classmethod60 def address1(cls):61 region = ad.Region.objects.get_or_create(name='Here', country='AU')[0]62 return ad.Address.objects.get_or_create(address='My House', post_code='1000', region=region)[0]63 @classmethod64 def address2(cls):65 region = ad.Region.objects.get_or_create(name='Here', country='AU')[0]66 return ad.Address.objects.get_or_create(address='My House 2', post_code='1000', region=region)[0]67 @classmethod68 def advisor1(cls):69 params = {70 'firm': Fixture1.firm1(),71 'betasmartz_agreement': True,72 'default_portfolio_set': Fixture1.portfolioset1(),73 'residential_address': Fixture1.address1(),74 }75 return Advisor.objects.get_or_create(user=Fixture1.advisor1_user(), defaults=params)[0]76 @classmethod77 def client1_user(cls):78 params = {79 'first_name': "test",80 'last_name': "client",81 }82 i, c = User.objects.get_or_create(email="client@example.com", defaults=params)83 if c:84 i.groups.add(Fixture1.user_group_clients())85 return i86 @classmethod87 def client2_user(cls):88 params = {89 'first_name': "test",90 'last_name': "client_2",91 }92 return User.objects.get_or_create(email="client2@example.com", defaults=params)[0]93 @classmethod94 def client1(cls):95 params = {96 'advisor': Fixture1.advisor1(),97 'user': Fixture1.client1_user(),98 'date_of_birth': datetime.date(1970, 1, 1),99 'residential_address': Fixture1.address2(),100 'risk_profile_group': Fixture1.risk_profile_group1(),101 }102 return Client.objects.get_or_create(id=1, defaults=params)[0]103 @classmethod104 def client2(cls):105 params = {106 'advisor': Fixture1.advisor1(),107 'user': Fixture1.client2_user(),108 'date_of_birth': datetime.date(1980, 1, 1),109 'residential_address': Fixture1.address2(),110 'risk_profile_group': Fixture1.risk_profile_group2(),111 }112 return Client.objects.get_or_create(id=2, defaults=params)[0]113 @classmethod114 def client1_retirementplan1(cls):115 return RetirementPlan.objects.get_or_create(id=1, defaults={116 'name': 'Plan1',117 'client': Fixture1.client1(),118 'desired_income': 60000,119 'income': 80000,120 'volunteer_days': 1,121 'paid_days': 2,122 'same_home': True,123 'reverse_mortgage': True,124 'expected_return_confidence': 0.5,125 'retirement_age': 65,126 'btc': 50000,127 'atc': 30000,128 'desired_risk': 0.6,129 'recommended_risk': 0.5,130 'max_risk': 1.0,131 'calculated_life_expectancy': 73,132 'selected_life_expectancy': 80,133 })[0]134 @classmethod135 def client2_retirementplan1(cls):136 return RetirementPlan.objects.get_or_create(id=2, defaults={137 'name': 'Plan1',138 'client': Fixture1.client2(),139 'desired_income': 60000,140 'income': 80000,141 'volunteer_days': 1,142 'paid_days': 2,143 'same_home': True,144 'reverse_mortgage': True,145 'expected_return_confidence': 0.5,146 'retirement_age': 65,147 'btc': 50000,148 'atc': 30000,149 'desired_risk': 0.6,150 'recommended_risk': 0.5,151 'max_risk': 1.0,152 'calculated_life_expectancy': 73,153 'selected_life_expectancy': 80,154 })[0]155 @classmethod156 def client2_retirementplan2(cls):157 return RetirementPlan.objects.get_or_create(id=4, defaults={158 'name': 'Plan2',159 'client': Fixture1.client2(),160 'desired_income': 60000,161 'income': 80000,162 'volunteer_days': 1,163 'paid_days': 2,164 'same_home': True,165 'reverse_mortgage': True,166 'expected_return_confidence': 0.5,167 'retirement_age': 65,168 'btc': 50000,169 'atc': 30000,170 'desired_risk': 0.6,171 'recommended_risk': 0.5,172 'max_risk': 1.0,173 'calculated_life_expectancy': 73,174 'selected_life_expectancy': 80,175 })[0]176 @classmethod177 def client1_partneredplan(cls):178 plan1 = Fixture1.client1_retirementplan1()179 plan2 = Fixture1.client2_retirementplan1()180 plan1.partner_plan = plan2181 plan2.partner_plan = plan1182 plan1.save()183 plan2.save()184 return plan1185 @classmethod186 def risk_profile_group1(cls):187 return RiskProfileGroup.objects.get_or_create(name='risk_profile_group1')[0]188 @classmethod189 def risk_profile_group2(cls):190 return RiskProfileGroup.objects.get_or_create(name='risk_profile_group2')[0]191 @classmethod192 def risk_profile_question1(cls):193 return RiskProfileQuestion.objects.get_or_create(group=Fixture1.risk_profile_group1(),194 order=0,195 text='How much do you like risk?')[0]196 @classmethod197 def risk_profile_question2(cls):198 return RiskProfileQuestion.objects.get_or_create(group=Fixture1.risk_profile_group1(),199 order=1,200 text='How sophisticated are you?')[0]201 @classmethod202 def risk_profile_question3(cls):203 return RiskProfileQuestion.objects.get_or_create(group=Fixture1.risk_profile_group1(),204 order=2,205 text='Have you used other wealth management software?')[0]206 @classmethod207 def risk_profile_answer1a(cls):208 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question1(),209 order=0,210 text='A lot',211 b_score=9,212 a_score=9,213 s_score=9)[0]214 @classmethod215 def risk_profile_answer1b(cls):216 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question1(),217 order=1,218 text='A little',219 b_score=2,220 a_score=2,221 s_score=2)[0]222 @classmethod223 def risk_profile_answer1c(cls):224 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question1(),225 order=2,226 text="I'm smart but scared",227 b_score=1,228 a_score=9,229 s_score=9)[0]230 @classmethod231 def risk_profile_answer1d(cls):232 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question1(),233 order=3,234 text="I'm clueless and wild",235 b_score=9,236 a_score=1,237 s_score=1)[0]238 @classmethod239 def risk_profile_answer2a(cls):240 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question2(),241 order=0,242 text='Very',243 b_score=9,244 a_score=9,245 s_score=9)[0]246 @classmethod247 def risk_profile_answer2b(cls):248 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question2(),249 order=1,250 text="I'm basically a peanut",251 b_score=1,252 a_score=1,253 s_score=1)[0]254 @classmethod255 def risk_profile_answer2c(cls):256 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question2(),257 order=2,258 text="I'm smart but scared",259 b_score=1,260 a_score=9,261 s_score=9)[0]262 @classmethod263 def risk_profile_answer2d(cls):264 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question2(),265 order=3,266 text="I'm clueless and wild",267 b_score=9,268 a_score=1,269 s_score=1)[0]270 @classmethod271 def risk_profile_answer3a(cls):272 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question3(),273 order=0,274 text='Yes',275 b_score=9,276 a_score=9,277 s_score=9)[0]278 @classmethod279 def risk_profile_answer3b(cls):280 return RiskProfileAnswer.objects.get_or_create(question=Fixture1.risk_profile_question3(),281 order=1,282 text='No',283 b_score=1,284 a_score=1,285 s_score=1)[0]286 @classmethod287 def populate_risk_profile_questions(cls):288 Fixture1.risk_profile_question1()289 Fixture1.risk_profile_answer1a()290 Fixture1.risk_profile_answer1b()291 Fixture1.risk_profile_question2()292 Fixture1.risk_profile_answer2a()293 Fixture1.risk_profile_answer2b()294 # Don't create question3 here, we use that to test validation295 # that risk is NEUTRAL when the questions have changed296 @classmethod297 def populate_risk_profile_responses(cls):298 Fixture1.personal_account1().primary_owner.risk_profile_responses.add(Fixture1.risk_profile_answer1a())299 Fixture1.personal_account1().primary_owner.risk_profile_responses.add(Fixture1.risk_profile_answer2a())300 @classmethod301 def ib_account1(cls) -> IBAccount:302 params = {303 'ib_account': 'DU299694',304 'bs_account': Fixture1.personal_account1()305 }306 return IBAccount.objects.get_or_create(id=1, defaults=params)[0]307 @classmethod308 def ib_account2(cls) -> IBAccount:309 params = {310 'ib_account': 'DU299695',311 'bs_account': Fixture1.personal_account2()312 }313 return IBAccount.objects.get_or_create(id=2, defaults=params)[0]314 @classmethod315 def personal_account1(cls) -> ClientAccount:316 params = {317 'account_type': ACCOUNT_TYPE_PERSONAL,318 'primary_owner': Fixture1.client1(),319 'default_portfolio_set': Fixture1.portfolioset1(),320 'confirmed': True,321 }322 return ClientAccount.objects.get_or_create(id=1, defaults=params)[0]323 @classmethod324 def personal_account2(cls) -> ClientAccount:325 params = {326 'account_type': ACCOUNT_TYPE_PERSONAL,327 'primary_owner': Fixture1.client2(),328 'default_portfolio_set': Fixture1.portfolioset2(),329 'confirmed': True,330 }331 return ClientAccount.objects.get_or_create(id=2, defaults=params)[0]332 @classmethod333 def metric_group1(cls):334 g, c = GoalMetricGroup.objects.get_or_create(type=GoalMetricGroup.TYPE_PRESET,335 name='metricgroup1')336 # A metric group isn't valid without a risk score, and we only want to create the metric if the group was337 # newly created.338 if c:339 GoalMetricFactory.create(group=g, configured_val=MINIMUM_RISK)340 return g341 @classmethod342 def metric_group2(cls):343 g, c = GoalMetricGroup.objects.get_or_create(type=GoalMetricGroup.TYPE_PRESET,344 name='metricgroup2')345 # A metric group isn't valid without a risk score, and we only want to create the metric if the group was346 # newly created.347 if c:348 GoalMetricFactory.create(group=g, configured_val=MINIMUM_RISK)349 return g350 @classmethod351 def settings1(cls):352 return GoalSetting.objects.get_or_create(target=100000,353 completion=datetime.date(2000, 1, 1),354 hedge_fx=False,355 metric_group=Fixture1.metric_group1(),356 rebalance=False)[0]357 @classmethod358 def settings2(cls):359 return GoalSetting.objects.get_or_create(target=100000,360 completion=datetime.date(2000, 1, 1),361 hedge_fx=False,362 metric_group=Fixture1.metric_group2(),363 rebalance=False)[0]364 @classmethod365 def goal_type1(cls):366 return GoalType.objects.get_or_create(name='goaltype1',367 default_term=5,368 risk_sensitivity=7.0)[0]369 @classmethod370 def goal_type2(cls):371 return GoalType.objects.get_or_create(name='goaltype2',372 default_term=5,373 risk_sensitivity=7.0)[0]374 @classmethod375 def goal1(cls):376 return Goal.objects.get_or_create(account=Fixture1.personal_account1(),377 name='goal1',378 type=Fixture1.goal_type1(),379 portfolio_set=Fixture1.portfolioset1(),380 selected_settings=Fixture1.settings1())[0]381 @classmethod382 def goal2(cls):383 return Goal.objects.get_or_create(account=Fixture1.personal_account2(),384 name='goal2',385 type=Fixture1.goal_type2(),386 portfolio_set=Fixture1.portfolioset2(),387 selected_settings=Fixture1.settings2())[0]388 @classmethod389 def settings_event1(cls):390 return Log.objects.get_or_create(user=Fixture1.client1_user(),391 timestamp=timezone.make_aware(datetime.datetime(2000, 1, 1)),392 action=Event.APPROVE_SELECTED_SETTINGS.name,393 extra={'reason': 'Just because'},394 defaults={'obj': Fixture1.goal1()})[0]395 @classmethod396 def settings_event2(cls):397 return Log.objects.get_or_create(user=Fixture1.client1_user(),398 timestamp=timezone.make_aware(datetime.datetime(2000, 1, 1, 1)),399 action=Event.UPDATE_SELECTED_SETTINGS.name,400 extra={'reason': 'Just because 2'},401 defaults={'obj': Fixture1.goal1()})[0]402 @classmethod403 def transaction_event1(cls):404 # This will populate the associated Transaction as well.405 return Log.objects.get_or_create(user=Fixture1.client1_user(),406 timestamp=timezone.make_aware(datetime.datetime(2001, 1, 1)),407 action=Event.GOAL_DEPOSIT_EXECUTED.name,408 extra={'reason': 'Goal Deposit',409 'txid': Fixture1.transaction1().id},410 defaults={'obj': Fixture1.goal1()})[0]411 @classmethod412 def transaction1(cls):413 i, c = Transaction.objects.get_or_create(reason=Transaction.REASON_DEPOSIT,414 to_goal=Fixture1.goal1(),415 amount=3000,416 status=Transaction.STATUS_EXECUTED,417 created=timezone.make_aware(datetime.datetime(2000, 1, 1)),418 executed=timezone.make_aware(datetime.datetime(2001, 1, 1)))419 if c:420 i.created = timezone.make_aware(datetime.datetime(2000, 1, 1))421 i.save()422 return i423 @classmethod424 def transaction2(cls):425 i, c = Transaction.objects.get_or_create(reason=Transaction.REASON_ORDER,426 to_goal=Fixture1.goal1(),427 amount=3000,428 status=Transaction.STATUS_PENDING,429 created=timezone.make_aware(datetime.datetime(2000, 1, 1))430 )431 if c:432 i.created = timezone.make_aware(datetime.datetime(2000, 1, 1))433 i.save()434 return i435 @classmethod436 def pending_deposit1(cls):437 i, c = Transaction.objects.get_or_create(reason=Transaction.REASON_DEPOSIT,438 to_goal=Fixture1.goal1(),439 amount=4000,440 status=Transaction.STATUS_PENDING,441 created=timezone.make_aware(datetime.datetime(2000, 1, 1, 1)))442 if c:443 i.created = timezone.make_aware(datetime.datetime(2000, 1, 1, 1))444 i.save()445 return i446 @classmethod447 def pending_withdrawal1(cls):448 i, c = Transaction.objects.get_or_create(reason=Transaction.REASON_DEPOSIT,449 from_goal=Fixture1.goal1(),450 amount=3500,451 status=Transaction.STATUS_PENDING,452 created=timezone.make_aware(datetime.datetime(2000, 1, 1, 2)))453 if c:454 i.created = timezone.make_aware(datetime.datetime(2000, 1, 1, 2))455 i.save()456 return i457 @classmethod458 def populate_balance1(cls):459 HistoricalBalance.objects.get_or_create(goal=Fixture1.goal1(),460 date=datetime.date(2000, 12, 31),461 balance=0)462 HistoricalBalance.objects.get_or_create(goal=Fixture1.goal1(),463 date=datetime.date(2001, 1, 1),464 balance=3000)465 @classmethod466 def asset_class1(cls):467 params = {468 'display_order': 0,469 'display_name': 'Test Asset Class 1',470 'investment_type_id': 2, # STOCKS pk 2, BONDS pk 1, MIXED pk 3471 }472 # Asset class name needs to be upper case.473 return AssetClass.objects.get_or_create(name='ASSETCLASS1', defaults=params)[0]474 @classmethod475 def region1(cls):476 return Region.objects.get_or_create(name='TestRegion1')[0]477 @classmethod478 def market_index1(cls):479 params = {480 'display_name': 'Test Market Index 1',481 'url': 'nowhere.com',482 'currency': 'AUD',483 'region': Fixture1.region1(),484 'data_api': 'portfolios.api.bloomberg',485 'data_api_param': 'MI1',486 }487 return MarketIndex.objects.get_or_create(id=1, defaults=params)[0]488 @classmethod489 def market_index1_daily_prices(cls):490 prices = [100, 110, 105, 103, 107]491 start_date = datetime.date(2016, 4, 1)492 d = start_date493 fund = cls.market_index1()494 for p in prices:495 fund.daily_prices.create(date=d, price=p)496 d += datetime.timedelta(1)497 @classmethod498 def market_index2(cls):499 params = {500 'display_name': 'Test Market Index 2',501 'url': 'nowhere.com',502 'currency': 'AUD',503 'region': Fixture1.region1(),504 'data_api': 'portfolios.api.bloomberg',505 'data_api_param': 'MI2',506 }507 return MarketIndex.objects.get_or_create(id=1, defaults=params)[0]508 @classmethod509 def fund1(cls):510 params = {511 'display_name': 'Test Fund 1',512 'url': 'nowhere.com/1',513 'currency': 'AUD',514 'region': Fixture1.region1(),515 'ordering': 0,516 'asset_class': Fixture1.asset_class1(),517 'benchmark': Fixture1.market_index1(),518 'data_api': 'portfolios.api.bloomberg',519 'data_api_param': 'FUND1',520 }521 return Ticker.objects.get_or_create(symbol='TSTSYMBOL1', defaults=params)[0]522 @classmethod523 def fund2(cls):524 params = {525 'display_name': 'Test Fund 2',526 'url': 'nowhere.com/2',527 'currency': 'AUD',528 'region': Fixture1.region1(),529 'ordering': 1,530 'asset_class': Fixture1.asset_class1(),531 'benchmark': Fixture1.market_index2(),532 'data_api': 'portfolios.api.bloomberg',533 'data_api_param': 'FUND2',534 }535 return Ticker.objects.get_or_create(symbol='TSTSYMBOL2', defaults=params)[0]536 @classmethod537 def fund3(cls):538 params = {539 'display_name': 'Test Fund 2',540 'url': 'nowhere.com/2',541 'currency': 'AUD',542 'region': Fixture1.region1(),543 'ordering': 1,544 'asset_class': Fixture1.asset_class1(),545 'benchmark': Fixture1.market_index2(),546 'data_api': 'portfolios.api.bloomberg',547 'data_api_param': 'FUND3',548 }549 return Ticker.objects.get_or_create(symbol='SPY', defaults=params)[0]550 @classmethod551 def fund4(cls):552 params = {553 'display_name': 'Test Fund 2',554 'url': 'nowhere.com/2',555 'currency': 'AUD',556 'region': Fixture1.region1(),557 'ordering': 1,558 'asset_class': Fixture1.asset_class1(),559 'benchmark': Fixture1.market_index2(),560 'data_api': 'portfolios.api.bloomberg',561 'data_api_param': 'FUND4',562 }563 return Ticker.objects.get_or_create(symbol='TLT', defaults=params)[0]564 @classmethod565 def external_instrument1(cls):566 params = {567 'institution': ExternalInstrument.Institution.APEX.value,568 'instrument_id': 'SPY_APEX',569 'ticker': Fixture1.fund3()570 }571 return ExternalInstrument.objects.get_or_create(id=1,defaults=params)[0]572 @classmethod573 def external_instrument2(cls):574 params = {575 'institution': ExternalInstrument.Institution.INTERACTIVE_BROKERS.value,576 'instrument_id': 'SPY_IB',577 'ticker': Fixture1.fund3()578 }579 return ExternalInstrument.objects.get_or_create(id=2,defaults=params)[0]580 @classmethod581 def external_debt_1(cls):582 params = {583 'type': ExternalAsset.Type.PROPERTY_LOAN.value,584 # description intentionally omitted to test optionality585 'valuation': '-145000',586 'valuation_date': datetime.date(2016, 7, 5),587 'growth': '0.03',588 'acquisition_date': datetime.date(2016, 7, 3),589 }590 return ExternalAsset.objects.get_or_create(name='My Home Loan', owner=Fixture1.client1(), defaults=params)[0]591 @classmethod592 def external_asset_1(cls):593 '''594 Creates and returns an asset with an associated debt.595 :return:596 '''597 params = {598 'type': ExternalAsset.Type.FAMILY_HOME.value,599 'description': 'This is my beautiful home',600 'valuation': '345000.014',601 'valuation_date': datetime.date(2016, 7, 5),602 'growth': '0.01',603 # trasfer_plan intentionally omitted as there isn't one604 'acquisition_date': datetime.date(2016, 7, 3),605 'debt': Fixture1.external_debt_1(),606 }607 return ExternalAsset.objects.get_or_create(name='My Home', owner=Fixture1.client1(), defaults=params)[0]608 @classmethod609 def set_prices(cls, prices):610 """611 Sets the prices for the given instruments and dates.612 :param prices:613 :return:614 """615 for asset, dstr, price in prices:616 DailyPrice.objects.update_or_create(instrument_object_id=asset.id,617 instrument_content_type=ContentType.objects.get_for_model(asset),618 date=datetime.datetime.strptime(dstr, '%Y%m%d'),619 defaults={'price': price})620 @classmethod621 def add_orders(cls, order_details):622 """623 Adds a bunch of orders to the system624 :param order_details: Iterable of (account, order_state) tuples.625 :return: the newly created orders as a list.626 """627 res = []628 for account, state in order_details:629 res.append(MarketOrderRequest.objects.create(state=state.value, account=account))630 return res631 @classmethod632 def add_executions(cls, execution_details):633 """634 Adds a bunch of order executions to the system635 :param execution_details: Iterable of (asset, order, volume, price, amount, time) tuples.636 :return: the newly created executions as a list.637 """638 res = []639 for asset, order, volume, price, amount, time in execution_details:640 res.append(Execution.objects.create(asset=asset,641 volume=volume,642 order=order,643 price=price,644 executed=timezone.make_aware(datetime.datetime.strptime(time, '%Y%m%d')),645 amount=amount))646 return res647 @classmethod648 def add_execution_distributions(cls, distribution_details):649 """650 Adds a bunch of order execution distributions to the system651 :param distribution_details: Iterable of (execution, volume, goal) tuples.652 :return: the newly created distributions as a list.653 """654 res = []655 for execution, volume, goal in distribution_details:656 amount = abs(execution.amount * volume / execution.volume)657 if volume > 0:658 tx = Transaction.objects.create(reason=Transaction.REASON_EXECUTION,659 from_goal=goal,660 amount=amount,661 status=Transaction.STATUS_EXECUTED,662 executed=execution.executed)663 else:664 tx = Transaction.objects.create(reason=Transaction.REASON_EXECUTION,665 to_goal=goal,666 amount=amount,667 status=Transaction.STATUS_EXECUTED,668 executed=execution.executed)669 tx.created = execution.executed670 tx.save()671 res.append(ExecutionDistribution.objects.create(execution=execution,672 transaction=tx,673 volume=volume))674 return res675 @classmethod676 def populate_observations(cls, values, begin_date):677 dates = list()678 values = str(values)679 for val in values:680 dates.append(begin_date)681 InvestmentCycleObservationFactory.create(as_of=begin_date,682 cycle=int(val))683 begin_date += datetime.timedelta(1)684 return dates685 @classmethod686 def create_execution_details(cls, goal, ticker, quantity, price, executed):687 mor = MarketOrderRequest.objects.create(state=MarketOrderRequest.State.COMPLETE.value, account=goal.account)688 execution = Execution.objects.create(asset=ticker,689 volume=quantity,690 order=mor,691 price=price,692 executed=executed,693 amount=quantity*price)694 transaction = TransactionFactory.create(reason=Transaction.REASON_EXECUTION,695 to_goal=None,696 from_goal=goal,697 status=Transaction.STATUS_EXECUTED,698 executed=executed,699 amount=quantity*price)700 distribution = ExecutionDistribution.objects.create(execution=execution,701 transaction=transaction,702 volume=quantity)...
Cinematic.spec.js
Source:Cinematic.spec.js
1/* eslint-env jasmine */2import {3 getLeftCharacterThangTypeSlug,4 getRightCharacterThangTypeSlug,5 getLeftHero,6 getRightHero,7 getClearBackgroundObject,8 getBackgroundObject,9 getBackgroundObjectDelay,10 getBackground,11 getClearText,12 getSpeaker,13 getBackgroundSlug,14 getExitCharacter,15 getTextPosition,16 getText,17 getCamera,18 getTextAnimationLength,19 getSpeakingAnimationAction,20 getSetupMusic,21 getSoundEffects,22 getWaitUserInput,23 getLanguageFilter,24 getHeroPet25} from '../../../app/schemas/models/selectors/cinematic'26/**27 * This data can be used to check that none of the selectors that match28 * the left or right character work.29 * Intentionally invalid data.30 */31const invalidThangTypesSetupData = [32 { shotSetup: {} },33 { dialogNodes: [] },34 { shotSetup: { camera: 'dual' } },35 { shotSetup: { rightThangType: {} } },36 { shotSetup: { backgroundArt: {} } },37 { shotSetup: { leftThangType: undefined } },38 { shotSetup: { leftThangType: { slug: 'abc', rand: 123 } } },39 { shotSetup: { rightThangType: undefined } },40 { shotSetup: { rightThangType: { slug: 'abc', rand: 123 } } }41]42describe('Cinematic', () => {43 describe('Selectors', () => {44 getCharacterThangTypeSlugTest(getLeftCharacterThangTypeSlug, 'getLeftCharacterThangTypeSlug', invalidThangTypesSetupData, 'leftThangType')45 getCharacterThangTypeSlugTest(getRightCharacterThangTypeSlug, 'getRightCharacterThangTypeSlug', invalidThangTypesSetupData, 'rightThangType')46 it('getLeftCharacterThangTypeSlug', () => {47 const result = getLeftCharacterThangTypeSlug(shotFixture1)48 expect(result).toBeUndefined()49 const result2 = getLeftCharacterThangTypeSlug(shotFixture2)50 expect(result2).toEqual({ slug: 'fake-slug-thangtype', enterOnStart: false, thang: { scaleX: 1.2, scaleY: 1.2, pos: { x: -30, y: -72 } } })51 })52 it('getRightCharacterThangTypeSlug', () => {53 const result = getRightCharacterThangTypeSlug(shotFixture2)54 expect(result).toBeUndefined()55 const result2 = getRightCharacterThangTypeSlug(shotFixture1)56 expect(result2).toEqual({ slug: 'fake-slug-thangtype', enterOnStart: false, thang: { scaleX: 1.2, scaleY: 1.2, pos: { x: 30, y: -72 } } })57 })58 it('getLeftHero', () => {59 const result = getLeftHero(shotFixture1)60 expect(result).toEqual({ enterOnStart: true, thang: { scaleX: 1, scaleY: 13, pos: { x: 3, y: 10 } } })61 const result2 = getLeftHero(shotFixture2)62 expect(result2).toBeUndefined()63 })64 it('getRightHero', () => {65 const result = getRightHero(shotFixture2)66 expect(result).toEqual({ enterOnStart: true, thang: { scaleX: 1, scaleY: 13, pos: { x: 3, y: 10 } } })67 const result2 = getRightHero(shotFixture1)68 expect(result2).toBeUndefined()69 })70 it('getClearBackgroundObject', () => {71 const result = getClearBackgroundObject(shotFixture1.dialogNodes[0])72 expect(result).toEqual(7331)73 const result2 = getClearBackgroundObject(shotFixture2.dialogNodes[0])74 expect(result2).toBeUndefined()75 })76 it('getBackgroundObject', () => {77 const result = getBackgroundObject(shotFixture1.dialogNodes[0])78 expect(result).toEqual({ scaleX: 1, scaleY: 1, pos: { x: 0, y: 0 }, type: { slug: 'background-obj-fixture' } })79 const result2 = getBackgroundObject(shotFixture2.dialogNodes[0])80 expect(result2).toBeUndefined()81 })82 it('getBackgroundObjectDelay', () => {83 const result = getBackgroundObjectDelay(shotFixture1.dialogNodes[0])84 expect(result).toEqual(1337)85 const result2 = getBackgroundObjectDelay(shotFixture2.dialogNodes[0])86 expect(result2).toBeUndefined()87 })88 it('getBackground', () => {89 const result = getBackground(shotFixture1)90 expect(result).toEqual({ slug: 'background-fixture-slug', thang: { scaleX: 0.3, scaleY: 0.2, pos: { x: 17, y: 18 } } })91 const result2 = getBackground(shotFixture2)92 expect(result2).toBeUndefined()93 })94 it('getClearText', () => {95 const result = getClearText(shotFixture1.dialogNodes[0])96 expect(result).toEqual(false)97 const result2 = getClearText(shotFixture2.dialogNodes[0])98 expect(result2).toEqual(true)99 })100 it('getSpeaker', () => {101 const result = getSpeaker(shotFixture1.dialogNodes[0])102 expect(result).toEqual('left')103 const result2 = getSpeaker(shotFixture2.dialogNodes[0])104 expect(result2).toEqual('right')105 })106 it('getBackgroundSlug', () => {107 const result = getBackgroundSlug(shotFixture1)108 expect(result).toEqual('background-fixture-slug')109 const result2 = getBackgroundSlug(shotFixture2)110 expect(result2).toBeUndefined()111 })112 it('getExitCharacter', () => {113 const result = getExitCharacter(shotFixture1.dialogNodes[0])114 expect(result).toEqual('both')115 const result2 = getExitCharacter(shotFixture2.dialogNodes[0])116 expect(result2).toBeUndefined()117 })118 it('getTextPosition', () => {119 const result = getTextPosition(shotFixture1.dialogNodes[0])120 expect(result).toBeUndefined()121 const result2 = getTextPosition(shotFixture2.dialogNodes[0])122 expect(result2).toEqual({123 x: 40,124 y: 10125 })126 })127 it('getText', () => {128 const result = getText(shotFixture1.dialogNodes[0])129 expect(result).toEqual('hello, world')130 const result2 = getText(shotFixture2.dialogNodes[0])131 expect(result2).toBeUndefined()132 })133 it('getCamera', () => {134 const result = getCamera(shotFixture1)135 expect(result).toEqual({ pos: { x: 2, y: 0 }, zoom: 2 })136 const result2 = getCamera(shotFixture2)137 expect(result2).toBeUndefined()138 expect(getCamera(undefined)).toBeUndefined()139 })140 it('getTextAnimationLength', () => {141 const result = getTextAnimationLength(shotFixture1.dialogNodes[1])142 expect(result).toEqual(42)143 const result2 = getTextAnimationLength(shotFixture2.dialogNodes[0])144 expect(result2).toBeUndefined()145 })146 it('getSpeakingAnimationAction', () => {147 const result = getSpeakingAnimationAction(shotFixture1.dialogNodes[1])148 expect(result).toEqual('talkyAnimation')149 const result2 = getSpeakingAnimationAction(shotFixture2.dialogNodes[0])150 expect(result2).toBeUndefined()151 })152 it('getSoundEffects', () => {153 const result = getSoundEffects(shotFixture1.dialogNodes[0])154 expect(result).toEqual([ { sound: { mp3: 'path/music' }, triggerStart: 0 } ])155 const result2 = getSoundEffects(shotFixture2.dialogNodes[0])156 expect(result2).toEqual([ { sound: { mp3: 'path/music' }, triggerStart: 30 } ])157 })158 it('getSetupMusic', () => {159 const result = getSetupMusic(shotFixture1)160 expect(result).toEqual({ ogg: 'path/music', mp3: 'path/music/mp3' })161 const result2 = getSetupMusic(shotFixture2)162 expect(result2).toBeUndefined()163 })164 it('getWaitUserInput', () => {165 expect(getWaitUserInput({ waitUserInput: false })).toEqual(false)166 expect(getWaitUserInput({})).toEqual(true)167 expect(getWaitUserInput()).toEqual(true)168 expect(getWaitUserInput({ waitUserInput: true })).toEqual(true)169 })170 it('getLanguageFilter', () => {171 expect(getLanguageFilter({})).toBeUndefined()172 expect(getLanguageFilter()).toBeUndefined()173 expect(getLanguageFilter({ programmingLanguageFilter: 'python' })).toEqual('python')174 expect(getLanguageFilter({ programmingLanguageFilter: 'javascript' })).toEqual('javascript')175 })176 it('getHeroPet', () => {177 const result = getHeroPet(shotFixture1)178 expect(result).toEqual({ slug: 'hero-dog-slug', thang: { scaleX: 1, scaleY: 2, pos: { x: 2, y: 0 } } })179 const result2 = getHeroPet(shotFixture2)180 expect(result2).toBeUndefined()181 })182 })183})184// Test for left and right character selectors.185function getCharacterThangTypeSlugTest (selector, side, data, characterProperty) {186 describe(`${side}`, () => {187 it('returns undefined when passed nothing', () => {188 expect(selector(undefined)).toBeUndefined()189 })190 it('returns undefined if the left thangType doesn\'t have type', () => {191 for (const testData of data) {192 expect(selector(testData)).toBeUndefined()193 }194 })195 })196}197// Fixture testing selectors for cinematics.198var shotFixture1 = {199 shotSetup: {200 leftThangType: {201 thangType: {202 type: 'hero',203 pos: {204 x: 3,205 y: 10206 },207 scaleX: 1,208 scaleY: 13209 },210 enterOnStart: true211 },212 rightThangType: {213 thangType: {214 type: {215 slug: 'fake-slug-thangtype'216 }217 }218 },219 heroPetThangType: {220 type: {221 slug: 'hero-dog-slug'222 },223 pos: {224 x: 2225 },226 scaleY: 2227 },228 backgroundArt: {229 type: {230 slug: 'background-fixture-slug'231 },232 pos: {233 x: 17,234 y: 18235 },236 scaleX: 0.3,237 scaleY: 0.2238 },239 camera: {240 pos: {241 x: 2242 },243 zoom: 2244 },245 music: {246 ogg: 'path/music',247 mp3: 'path/music/mp3'248 }249 },250 dialogNodes: [251 {252 dialogClear: false,253 exitCharacter: 'both',254 text: 'hello, world',255 triggers: {256 backgroundObject: {257 thangType: {258 type: {259 slug: 'background-obj-fixture'260 }261 },262 triggerStart: 1337263 },264 clearBackgroundObject: {265 triggerStart: 7331266 },267 soundFxTriggers: [268 {269 sound: {270 mp3: 'path/music'271 }272 }273 ]274 }275 },276 {277 dialogClear: false,278 textAnimationLength: 42,279 speakingAnimationAction: 'talkyAnimation'280 }281 ]282}283var shotFixture2 = {284 shotSetup: {285 rightThangType: {286 thangType: {287 type: 'hero',288 pos: {289 x: 3,290 y: 10291 },292 scaleX: 1,293 scaleY: 13294 },295 enterOnStart: true296 },297 leftThangType: {298 thangType: {299 type: {300 slug: 'fake-slug-thangtype'301 }302 }303 }304 },305 dialogNodes: [306 {307 speaker: 'right',308 triggers: {309 soundFxTriggers: [310 {311 sound: {312 mp3: 'path/music'313 },314 triggerStart: 30315 }316 ]317 },318 textLocation: {319 x: 40,320 y: 10321 }322 }323 ]...
test_risk_profiler.py
Source:test_risk_profiler.py
1from django.core.exceptions import ValidationError2from django.test import TestCase3from api.v1.tests.factories import RiskProfileAnswerFactory, RiskProfileQuestionFactory4from main.models import GoalMetric5from main.risk_profiler import recommend_risk, max_risk, MINIMUM_RISK, validate_risk_score6from main.tests.fixture import Fixture17class RiskProfilerTests(TestCase):8 def test_recommend_risk_no_questions(self):9 goal = Fixture1.goal1()10 settings = Fixture1.settings1()11 account = settings.goal.account12 self.assertEqual(recommend_risk(settings), MINIMUM_RISK)13 def test_recommend_risk_fully_unanswered(self):14 # Populate the questions, we should still get 0.515 goal = Fixture1.goal1()16 settings = Fixture1.settings1()17 account = settings.goal.account18 Fixture1.populate_risk_profile_questions()19 self.assertEqual(recommend_risk(settings), MINIMUM_RISK)20 def test_recommend_risk_partially_unanswered(self):21 # Partially populate the answers, we should still get 0.522 goal = Fixture1.goal1()23 settings = Fixture1.settings1()24 account = settings.goal.account25 Fixture1.populate_risk_profile_questions()26 Fixture1.risk_profile_answer1a()27 self.assertEqual(recommend_risk(settings), MINIMUM_RISK)28 def test_recommend_risk_fully_answered_bad_questions(self):29 # Fully populate the answers, but no range in the available question responses, we should get 0.530 goal = Fixture1.goal1()31 settings = Fixture1.settings1()32 account = settings.goal.account33 Fixture1.populate_risk_profile_questions() # Also populates all possible answers.34 Fixture1.populate_risk_profile_responses()35 Fixture1.risk_profile_question3() # Add a question we don't have an answer for36 self.assertEqual(recommend_risk(settings), MINIMUM_RISK)37 # Now answer the question, we shouldn't get MINIMUM_RISK38 account.primary_owner.risk_profile_responses.add(Fixture1.risk_profile_answer3a())39 self.assertNotEqual(recommend_risk(settings), MINIMUM_RISK)40 def test_fully_answered_zero_max(self):41 goal = Fixture1.goal1()42 setting = Fixture1.settings1()43 risk_metric = setting.metric_group.metrics.get(type=GoalMetric.METRIC_TYPE_RISK_SCORE)44 q = RiskProfileQuestionFactory.create(group=goal.account.primary_owner.risk_profile_group)45 a = RiskProfileAnswerFactory.create(b_score=0, a_score=0, s_score=0, question=q)46 client = goal.account.primary_owner47 client.risk_profile_responses.add(a)48 self.assertGreater(risk_metric.configured_val, 0.01)49 with self.assertRaises(ValidationError) as ex:50 validate_risk_score(setting)51 risk_metric.configured_val = 052 risk_metric.save()53 self.assertEqual(validate_risk_score(setting), None) # Should now complete OK.54 self.assertEqual(max_risk(setting), 0.0)55 self.assertEqual(recommend_risk(setting), 0.0)56 def test_recommend_risk_fully_answered(self):57 # Fully populate the answers, we should get 0.558 goal = Fixture1.goal1()59 settings = Fixture1.settings1()60 Fixture1.populate_risk_profile_questions() # Also populates all possible answers.61 Fixture1.populate_risk_profile_responses()62 self.assertEqual(recommend_risk(settings), 1.0)63 def test_recommend_risk_no_weights(self):64 goal = Fixture1.goal1()65 settings = Fixture1.settings1()66 self.assertEqual(recommend_risk(settings), MINIMUM_RISK)67 def test_recommend_risk(self):68 goal = Fixture1.goal1()69 settings = Fixture1.settings1()70 client = goal.account.primary_owner71 # Add the weights for the risk factors72 Fixture1.populate_risk_profile_questions() # Also populates all possible answers.73 Fixture1.populate_risk_profile_responses()74 # First lets start with the test_client, who scored 9 for all B,A,S75 # A goal of 80% of the value on a all-9s account is a bad idea76 # It's the lowest possible score77 settings.goal.account.primary_owner.net_worth = 10078 settings.goal.cash_balance = 8079 self.assertAlmostEqual(recommend_risk(settings), 0.10, 2)80 # A goal of 50% of the value is just as bad81 settings.goal.account.primary_owner.net_worth = 10082 settings.goal.cash_balance = 5083 self.assertAlmostEqual(recommend_risk(settings), 0.10, 2)84 # A goal of 10% of the value on a all-9s account is 1.085 # meaning this is the safest possible bet86 settings.goal.account.primary_owner.net_worth = 10087 settings.goal.cash_balance = 1088 self.assertAlmostEqual(recommend_risk(settings), 1.0, 2)89 # A goal of 33% of the value on a all-9s account is about 0.590 # Even if you are risky, sophisticated and rich, 30% is a lot91 settings.goal.account.primary_owner.net_worth = 10092 settings.goal.cash_balance = 3393 self.assertAlmostEqual(recommend_risk(settings), 0.5, 1)94 # For a new investor, the best possible suggestion is 10% or less95 settings.goal.account.primary_owner.net_worth = 10096 settings.goal.cash_balance = 1097 client.risk_profile_responses.clear()98 client.risk_profile_responses.add(Fixture1.risk_profile_answer1b())99 client.risk_profile_responses.add(Fixture1.risk_profile_answer2b())100 self.assertAlmostEqual(recommend_risk(settings), 0.2, 1)101 def test_max_risk(self):102 goal = Fixture1.goal1()103 settings = Fixture1.settings1()104 client = goal.account.primary_owner105 # Add the weights for the risk factors106 Fixture1.populate_risk_profile_questions() # Also populates all possible answers.107 Fixture1.populate_risk_profile_responses()108 # we haven't set a net worth or a target, so worth_score isn't a factor109 # An all-9s account will have a max_risk of 1110 self.assertEqual(max_risk(settings), 1.0)111 # and if they are low risk behavior, high Ability + Sophistication112 # the max risk is still 1113 client.risk_profile_responses.clear()114 client.risk_profile_responses.add(Fixture1.risk_profile_answer1c())115 client.risk_profile_responses.add(Fixture1.risk_profile_answer2c())116 self.assertEqual(max_risk(settings), 1.0)117 # but if they are risky, new and unskilled, recommend no risk118 client.risk_profile_responses.clear()119 client.risk_profile_responses.add(Fixture1.risk_profile_answer1d())120 client.risk_profile_responses.add(Fixture1.risk_profile_answer2d())121 self.assertAlmostEqual(recommend_risk(settings), 0.1, 1)...
read-frontmatter.js
Source:read-frontmatter.js
1import parse from '../../lib/read-frontmatter.js'2const filepath = 'path/to/file.md'3const fixture1 = `---4title: Hello, World5meaning_of_life: 426---7I am content.8`9describe('frontmatter', () => {10 it('parses frontmatter and content in a given string (no options required)', () => {11 const { data, content, errors } = parse(fixture1)12 expect(data.title).toBe('Hello, World')13 expect(data.meaning_of_life).toBe(42)14 expect(content.trim()).toBe('I am content.')15 expect(errors.length).toBe(0)16 })17 describe('frontmatter.stringify', () => {18 it('is exported', () => {19 expect(typeof parse.stringify).toBe('function')20 })21 })22 describe('YML parsing errors', () => {23 it('creates errors if YML has an unescaped quote', () => {24 const fixture = `---25intro: 'I've got an unescaped quote'26---27I am content.28`29 const { errors } = parse(fixture, { filepath })30 expect(errors.length).toBe(1)31 const expectedError = {32 filepath: 'path/to/file.md',33 message: 'YML parsing error!',34 reason: 'invalid frontmatter entry',35 }36 expect(errors[0]).toEqual(expectedError)37 })38 it('creates errors if YML has incorrect indentation', () => {39 const fixture = `---40title: Hello, World41 intro: 'I have a bad leading space'42---43I am content.44`45 const { errors } = parse(fixture, { filepath })46 expect(errors.length).toBe(1)47 const expectedError = {48 filepath: 'path/to/file.md',49 message: 'YML parsing error!',50 reason: 'bad indentation of a mapping entry',51 }52 expect(errors[0]).toEqual(expectedError)53 })54 })55 describe('schema', () => {56 it('is optional', () => {57 const schema = {58 properties: {59 title: {60 type: 'string',61 },62 meaning_of_life: {63 type: 'number',64 },65 },66 }67 const { data, content, errors } = parse(fixture1, { schema })68 expect(data.title).toBe('Hello, World')69 expect(data.meaning_of_life).toBe(42)70 expect(content.trim()).toBe('I am content.')71 expect(errors.length).toBe(0)72 })73 it('creates errors if frontmatter does not conform to schema', () => {74 const schema = {75 properties: {76 meaning_of_life: {77 type: 'number',78 minimum: 50,79 },80 },81 }82 const { data, content, errors } = parse(fixture1, { schema })83 expect(data.title).toBe('Hello, World')84 expect(data.meaning_of_life).toBe(42)85 expect(content.trim()).toBe('I am content.')86 expect(errors.length).toBe(1)87 const expectedError = {88 attribute: 'minimum',89 property: 'meaning_of_life',90 expected: 50,91 actual: 42,92 message: 'must be greater than or equal to 50',93 }94 expect(errors[0]).toEqual(expectedError)95 })96 it('creates errors if required frontmatter is not present', () => {97 const schema = {98 properties: {99 yet_another_key: {100 type: 'string',101 required: true,102 },103 },104 }105 const { errors } = parse(fixture1, { schema })106 expect(errors.length).toBe(1)107 const expectedError = {108 attribute: 'required',109 property: 'yet_another_key',110 expected: true,111 actual: undefined,112 message: 'is required',113 }114 expect(errors[0]).toEqual(expectedError)115 })116 })117 describe('validateKeyNames', () => {118 const schema = {119 properties: {120 age: {121 type: 'number',122 },123 },124 }125 it('creates errors for undocumented keys if `validateKeyNames` is true', () => {126 const { errors } = parse(fixture1, { schema, validateKeyNames: true, filepath })127 expect(errors.length).toBe(2)128 const expectedErrors = [129 {130 property: 'title',131 message: 'not allowed. Allowed properties are: age',132 filepath: 'path/to/file.md',133 },134 {135 property: 'meaning_of_life',136 message: 'not allowed. Allowed properties are: age',137 filepath: 'path/to/file.md',138 },139 ]140 expect(errors).toEqual(expectedErrors)141 })142 it('does not create errors for undocumented keys if `validateKeyNames` is false', () => {143 const { errors } = parse(fixture1, { schema, validateKeyNames: false })144 expect(errors.length).toBe(0)145 })146 })147 describe('validateKeyOrder', () => {148 it('creates errors if `validateKeyOrder` is true and keys are not in order', () => {149 const schema = {150 properties: {151 meaning_of_life: {152 type: 'number',153 },154 title: {155 type: 'string',156 },157 },158 }159 const { errors } = parse(fixture1, { schema, validateKeyOrder: true, filepath })160 const expectedErrors = [161 {162 property: 'keys',163 message:164 'keys must be in order. Current: title,meaning_of_life; Expected: meaning_of_life,title',165 filepath: 'path/to/file.md',166 },167 ]168 expect(errors).toEqual(expectedErrors)169 })170 it('does not create errors if `validateKeyOrder` is true and keys are in order', () => {171 const schema = {172 properties: {173 title: {174 type: 'string',175 },176 meaning_of_life: {177 type: 'number',178 },179 },180 }181 const { errors } = parse(fixture1, { schema, validateKeyOrder: true })182 expect(errors.length).toBe(0)183 })184 it('does not create errors if `validateKeyOrder` is true and expected keys are in order', () => {185 const schema = {186 properties: {187 title: {188 type: 'string',189 required: true,190 },191 yet_another_key: {192 type: 'string',193 },194 meaning_of_life: {195 type: 'number',196 required: true,197 },198 },199 }200 const { errors } = parse(fixture1, { schema, validateKeyOrder: true })201 expect(errors.length).toBe(0)202 })203 })...
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!!