Best Python code snippet using playwright-python
app.py
Source:app.py
1# -* -coding: utf-8 -*-2from ..log import app_log, error_log, auth_log, log_with_user3from pyramid.authentication import SessionAuthenticationPolicy4from pyramid.authorization import ACLAuthorizationPolicy5from pyramid.events import NewRequest6from pyramid.security import NO_PERMISSION_REQUIRED7import hashlib8import json9import mimetypes10import pkg_resources11import pyramid.config12import pyramid.events13import pyramid.httpexceptions14import pyramid.i18n15import pyramid.settings16import pyramid.threadlocal17import pyramid.view18import risclog.sqlalchemy.serializer19import signal20import sw.allotmentclub21import sw.allotmentclub.application22import sw.allotmentclub.browser23import sw.allotmentclub.browser.auth24import sw.allotmentclub.browser.base25import sw.allotmentclub.version26import time27import traceback28import zope.component29import sentry_sdk30from sentry_sdk.integrations.pyramid import PyramidIntegration31from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration32def json_serializer(value, default, **kw):33 return json.dumps(value, default=default, **kw)34def check_csrf(request):35 token = request.session.get_csrf_token()36 csrf_token = request.POST.get('csrf_token')37 return token == str(csrf_token)38class WSGIWrapper(object):39 def __init__(self, app, **settings):40 self.app = app41 self.settings = settings42 def __call__(self, environ, start_response):43 def start_response_wrapper(status, headers, exc_info=None):44 self.mangle_headers(headers)45 return start_response(status, headers, exc_info)46 return self.app(environ, start_response_wrapper)47class DenyFrame(WSGIWrapper):48 def mangle_headers(self, headers):49 headers.append(('X-Frame-Options', 'SAMEORIGIN'))50class NoCache(WSGIWrapper):51 def mangle_headers(self, headers):52 headers.append(('cache-control', 'no-cache'))53 headers.append(('cache-control', 'no-store'))54 headers.append(('pragma', 'no-cache'))55class Portal(sw.allotmentclub.application.Application):56 """The portal application."""57 def __call__(self, global_config, **settings):58 super(Portal, self).__call__(**settings)59 app = self.make_wsgi_app(global_config)60 return app61 def configure(self):62 super(Portal, self).configure()63 self.configure_zca()64 mimetypes.add_type('application/font-woff', '.woff')65 mimetypes.add_type('application/x-font-ttf', '.ttf')66 registry = pyramid.registry.Registry(67 bases=(zope.component.getGlobalSiteManager(),))68 if self.settings.get('sentry.dsn', None):69 version = sw.allotmentclub.version.__version__70 sentry_sdk.init(71 release=f"sw-allotmentclub-backend@{version}",72 dsn=self.settings['sentry.dsn'],73 integrations=[PyramidIntegration(), SqlalchemyIntegration()])74 self.config = config = pyramid.config.Configurator(75 settings=self.settings,76 registry=registry)77 config.setup_registry(settings=self.settings)78 config.include('pyramid_beaker')79 config.include('pyramid_exclog')80 config.include('pyramid_tm')81 if self.testing:82 config.include('pyramid_mailer.testing')83 elif config.registry.settings.get('mail.enable') == 'false':84 config.include('sw.allotmentclub.printmailer')85 else:86 config.include('sw.allotmentclub.logmailer')87 # self.add_csrf_check()88 config.add_renderer(89 'json', risclog.sqlalchemy.serializer.json_renderer_factory(90 serializer=json_serializer))91 config.set_default_permission('view')92 config.set_authentication_policy(SessionAuthenticationPolicy())93 config.set_authorization_policy(ACLAuthorizationPolicy())94 config.set_root_factory(95 sw.allotmentclub.browser.auth.get_default_context)96 config.add_request_method(97 sw.allotmentclub.browser.auth.get_user, 'user', property=True)98 self.add_routes()99 config.scan(package=sw.allotmentclub.browser,100 ignore=sw.allotmentclub.SCAN_IGNORE_TESTS)101 def configure_zca(self):102 pass103 def add_csrf_check(self):104 def check_request_for_csrf(event):105 request = event.request106 if request.POST and not check_csrf(request):107 # disable csrf check for login108 if request.path == '/login':109 return110 raise sw.allotmentclub.browser.interfaces.CSRFForbidden()111 self.config.add_subscriber(check_request_for_csrf, NewRequest)112 def add_routes(self):113 config = self.config114 config.add_route('sentry_frontend', '/sentry_frontend')115 config.add_route('login', '/login')116 config.add_route('logout', '/logout')117 config.add_route('navigation', '/navigation')118 config.add_route('home', '/')119 config.add_route('map', '/map')120 config.add_route('infobrief_print', '/infobrief_print')121 config.add_route('calendar', '/calendar')122 config.add_route('calendar_event_add', '/calendar/add')123 config.add_route(124 'calendar_event_delete',125 '/calendar/{id}/delete',126 factory='..model.Event.context_factory'127 )128 config.add_route('map_download', '/map/download')129 config.add_route('parcel_list', '/parcels')130 config.add_route('parcel_map_upload', '/parcels/{id}/upload_map',131 factory='..model.Parcel.context_factory')132 config.add_route('parcel_map_download_check',133 '/parcels/{id}/check_download_map',134 factory='..model.Parcel.context_factory')135 config.add_route('parcel_map_download', '/parcels/{id}/download_map',136 factory='..model.Parcel.context_factory')137 config.add_route('depot', '/depot')138 config.add_route('mail_add', '/mail/add')139 config.add_route('mail_list_inbox', '/mail/inbox')140 config.add_route('mail_list_sent', '/mail/sent')141 config.add_route('mail_list_drafts', '/mail/drafts')142 config.add_route('mail_edit', '/mail/{id}/edit',143 factory='..mail.Message.context_factory')144 config.add_route('mail_reply', '/mail/{id}/reply',145 factory='..mail.Message.context_factory')146 config.add_route('mail_status', '/mail/{id}/status',147 factory='..mail.Message.context_factory')148 config.add_route('mail_preview', '/mail/{id}/preview',149 factory='..mail.Message.context_factory')150 config.add_route('mail_print', '/mail/{id}/download',151 factory='..mail.Message.context_factory')152 config.add_route('mail_send', '/mail/{id}/send',153 factory='..mail.Message.context_factory')154 config.add_route('mail_duplicate', '/mail/{id}/duplicate',155 factory='..mail.Message.context_factory')156 config.add_route('mail_delete', '/mail/{id}/delete',157 factory='..mail.Message.context_factory')158 config.add_route('mail_list_attachments', '/mail/{id}/attachments',159 factory='..mail.Message.context_factory')160 config.add_route('mail_attachment_del', '/mail/attachment/{id}/del',161 factory='..mail.Attachment.context_factory')162 config.add_route('mail_attachment_white_page',163 '/mail/attachment/{id}/change_white_page',164 factory='..mail.Attachment.context_factory')165 config.add_route('mail_attachment_download',166 '/mail/attachment/{id}/download',167 factory='..mail.Attachment.context_factory')168 config.add_route('mail_postmark_open_tracking_webhook',169 '/mail/postmark_open_tracking_webhook')170 config.add_route('mail_postmark_inbound_webhook',171 '/mail/postmark_inbound_webhook')172 config.add_route('member_assignments', '/members/assignment_attendees')173 config.add_route('member_assignments_bill',174 '/members/assignment_attendees/bill')175 config.add_route('member_assignments_detail',176 '/members/assignment_attendees/{id}/list',177 factory='..model.Member.context_factory')178 config.add_route('member_add', '/members/add')179 config.add_route('member_edit',180 '/members/{id}/edit',181 factory='..model.Member.context_factory')182 config.add_route('member_attachment_add',183 '/members/{id}/attachments/add',184 factory='..model.Member.context_factory')185 config.add_route('member_attachment', '/members/{id}/attachments',186 factory='..model.Member.context_factory')187 config.add_route(188 'member_attachment_download',189 '/members/{member_id}/attachments/{id}/download',190 factory='..model.MemberAttachment.context_factory')191 config.add_route(192 'member_attachment_delete',193 '/members/{member_id}/attachments/{id}/delete',194 factory='..model.MemberAttachment.context_factory')195 config.add_route('member_account_list', '/members/account_list')196 config.add_route('member_account_detail_list',197 '/members/{id}/account_details',198 factory='..model.Member.context_factory')199 config.add_route(200 'member_account_detail_switch_ir',201 '/members/{member_id}/account_details/{id}/switch',202 factory='.account.AccountDetailFactory')203 config.add_route('banking_account_list', '/accounts/list')204 config.add_route('banking_account_list_detail',205 '/accounts/{id}/detail',206 factory='..account.BookingKind.context_factory')207 config.add_route('banking_account_list_report', '/accounts/report.pdf')208 config.add_route('sepa_sammler_list', '/accounts/sepa_sammler')209 config.add_route('sepa_sammler_add', '/accounts/sepa_sammler/add')210 config.add_route(211 'sepa_ueberweisung_add',212 '/accounts/sepa_sammler/add_ueberweisung'213 )214 config.add_route('sepa_sammler_edit',215 '/accounts/sepa_sammler/{id}/edit',216 factory='..account.SEPASammler.context_factory')217 config.add_route('sepa_sammler_export',218 '/accounts/sepa_sammler/{id}/export',219 factory='..account.SEPASammler.context_factory')220 config.add_route('sepa_sammler_update',221 '/accounts/sepa_sammler/{id}/update',222 factory='..account.SEPASammler.context_factory')223 config.add_route('sepa_sammler_entry_list',224 '/accounts/sepa_sammler/{id}/entries',225 factory='..account.SEPASammler.context_factory')226 config.add_route('sepa_direct_debit', '/accounts/sepa_direct_debit')227 config.add_route('booking_list', '/accounts/{id}/list',228 factory='..account.BankingAccount.context_factory')229 config.add_route('split_booking', '/booking/{id}/split',230 factory='..account.Booking.context_factory')231 config.add_route('map_booking', '/booking/{id}/map',232 factory='..account.Booking.context_factory')233 config.add_route('waste_water', '/waste_water')234 config.add_route('property_tax_b', '/property_tax_b')235 config.add_route('member_list', '/members')236 config.add_route('member_list_leased', '/members_leased')237 config.add_route('member_list_passive', '/members_passive')238 config.add_route('member_list_tap_water', '/members_tap_water')239 config.add_route('member_sale_history', '/members/sale_history')240 config.add_route('membership_fee', '/members/insert_membership_fee')241 config.add_route('member_sale',242 '/members/{id}/sale',243 factory='..model.Member.context_factory')244 config.add_route('direct_debit_letter',245 '/members/{id}/direct_debit_letter',246 factory='..model.Member.context_factory')247 config.add_route('become_member_letter',248 '/members/{id}/become_member_letter',249 factory='..model.Member.context_factory')250 config.add_route('mv_entrance_list', '/members/mv_entrance_list')251 config.add_route('energy_meter_export', '/electricity/export')252 config.add_route('energy_meter_import', '/electricity/import')253 config.add_route('calculate_energy_values',254 '/electricity/calculate_energy_values')255 config.add_route('access_authority', '/access_authority')256 config.add_route('access_authority_detail',257 '/access_authority/{id}/list',258 factory='.authority.AuthorityContext.context_factory')259 config.add_route('access_authority_detail_add',260 '/access_authority/{id}/list/add',261 factory='.authority.AuthorityContext.context_factory')262 config.add_route(263 'access_authority_detail_edit',264 '/access_authority/{viewname}/list/{id}/edit',265 factory='..model.AccessAuthority.context_factory')266 config.add_route(267 'access_authority_detail_delete',268 '/access_authority/{viewname}/list/{id}/delete',269 factory='..model.AccessAuthority.context_factory')270 config.add_route('energy_price', '/energy_price')271 config.add_route('electricity_list', '/electricity')272 config.add_route('global_energy_value_list', '/electricity_billing')273 config.add_route('energy_value_list',274 '/electricity/{id}/history',275 factory='..electricity.ElectricMeter.context_factory')276 config.add_route('advance_pay_value_list',277 '/electricity/{id}/advance_pay_history',278 factory='..electricity.ElectricMeter.context_factory')279 config.add_route('externals', '/externals')280 config.add_route('external_add', '/externals/add')281 config.add_route('external_edit', '/externals/{id}/edit',282 factory='..mail.ExternalRecipient.context_factory')283 config.add_route('external_delete', '/externals/{id}/delete',284 factory='..mail.ExternalRecipient.context_factory')285 config.add_route('bulletins', '/bulletins')286 config.add_route('bulletin_add', '/bulletins/add')287 config.add_route('bulletin_edit', '/bulletins/{id}/edit',288 factory='..bulletins.Bulletin.context_factory')289 config.add_route('bulletin_delete', '/bulletins/{id}/delete',290 factory='..bulletins.Bulletin.context_factory')291 config.add_route('bulletin_print', '/bulletins/{id}/print',292 factory='..bulletins.Bulletin.context_factory')293 config.add_route('keylists', '/keylists')294 config.add_route('keylist_add', '/keylists/add')295 config.add_route('keylist_edit', '/keylists/{id}/edit',296 factory='..keylist.Keylist.context_factory')297 config.add_route('keylist_delete', '/keylists/{id}/delete',298 factory='..keylist.Keylist.context_factory')299 config.add_route('keys', '/keylists/{id}/keys',300 factory='..keylist.Keylist.context_factory')301 config.add_route('key_add', '/keylists/{id}/keys/add',302 factory='..keylist.Keylist.context_factory')303 config.add_route(304 'key_edit',305 '/keylists/{keylist_id}/keys/{id}/edit',306 factory='..keylist.Key.context_factory')307 config.add_route(308 'key_delete',309 '/keylists/{keylist_id}/keys/{id}/delete',310 factory='..keylist.Key.context_factory')311 config.add_route('keylist_attachment_add',312 '/keylists/{id}/attachments/add',313 factory='..keylist.Keylist.context_factory')314 config.add_route('keylist_attachment', '/keylists/{id}/attachments',315 factory='..keylist.Keylist.context_factory')316 config.add_route(317 'keylist_attachment_download',318 '/keylists/{keylist_id}/attachments/{id}/download',319 factory='..keylist.KeylistAttachment.context_factory')320 config.add_route('protocols', '/protocols')321 config.add_route('protocol_add', '/protocols/add')322 config.add_route('protocol_edit', '/protocols/{id}/edit',323 factory='..protocol.Protocol.context_factory')324 config.add_route('protocol_delete', '/protocols/{id}/delete',325 factory='..protocol.Protocol.context_factory')326 config.add_route('protocol_detail', '/protocols/{id}/details',327 factory='..protocol.Protocol.context_factory')328 config.add_route('protocol_detail_add', '/protocols/{id}/details/add',329 factory='..protocol.Protocol.context_factory')330 config.add_route(331 'protocol_detail_edit',332 '/protocols/{protocol_id}/details/{id}/edit',333 factory='..protocol.ProtocolDetail.context_factory')334 config.add_route(335 'protocol_detail_delete',336 '/protocols/{protocol_id}/details/{id}/delete',337 factory='..protocol.ProtocolDetail.context_factory')338 config.add_route('protocol_print', '/protocols/{id}/print',339 factory='..protocol.Protocol.context_factory')340 config.add_route('protocol_attachment_add',341 '/protocols/{id}/attachments/add',342 factory='..protocol.Protocol.context_factory')343 config.add_route('protocol_attachment', '/protocols/{id}/attachments',344 factory='..protocol.Protocol.context_factory')345 config.add_route(346 'protocol_attachment_download',347 '/protocols/{protocol_id}/attachments/{id}/download',348 factory='..protocol.ProtocolAttachment.context_factory')349 config.add_route(350 'protocol_attachment_delete',351 '/protocols/{protocol_id}/attachments/{id}/delete',352 factory='..protocol.ProtocolAttachment.context_factory')353 config.add_route('protocol_commitment', '/protocols/{id}/commitments',354 factory='..protocol.Protocol.context_factory')355 config.add_route('protocol_commitment_add',356 '/protocols/{id}/commitments/add',357 factory='..protocol.Protocol.context_factory')358 config.add_route(359 'protocol_commitment_edit',360 '/protocols/{protocol_id}/commitments/{id}/edit',361 factory='..protocol.ProtocolCommitment.context_factory')362 config.add_route(363 'protocol_commitment_delete',364 '/protocols/{protocol_id}/commitments/{id}/delete',365 factory='..protocol.ProtocolCommitment.context_factory')366 config.add_route('assignments', '/assignments')367 config.add_route('assignment_add', '/assignments/add')368 config.add_route('assignment_edit', '/assignments/{id}/edit',369 factory='..assignment.Assignment.context_factory')370 config.add_route('assignment_delete', '/assignments/{id}/delete',371 factory='..assignment.Assignment.context_factory')372 config.add_route('assignment_list_attendees', '/assignments/{id}/list',373 factory='..assignment.Assignment.context_factory')374 config.add_route('assignment_attendees_add',375 '/assignments/{id}/attendees/add',376 factory='..assignment.Assignment.context_factory')377 config.add_route(378 'assignment_attendees_edit',379 '/assignments/{assignment_id}/attendees/{id}/edit',380 factory='..assignment.AssignmentAttendee.context_factory')381 config.add_route(382 'assignment_attendees_delete',383 '/assignments/{assignment_id}/attendees/{id}/delete',384 factory='..assignment.AssignmentAttendee.context_factory')385 config.add_route('depots', '/depots')386 config.add_route('depot_add', '/depots/add')387 config.add_route('depot_edit', '/depots/{id}/edit',388 factory='..depot.Depot.context_factory')389 config.add_route('depot_delete', '/depots/{id}/delete',390 factory='..depot.Depot.context_factory')391 config.add_route('depot_download', '/depots/{id}/download',392 factory='..depot.Depot.context_factory')393 @property394 def pipeline(self):395 return [396 (DenyFrame, 'factory', None, {}),397 (NoCache, 'factory', None, {}),398 ]399 def make_wsgi_app(self, global_config):400 app = self.config.make_wsgi_app()401 for spec, protocol, name, extra in self.pipeline:402 if protocol == 'factory':403 app = spec(app, **extra)404 continue405 entrypoint = pkg_resources.get_entry_info(spec, protocol, name)406 app = entrypoint.load()(app, global_config, **extra)407 return app408factory = Portal()409@pyramid.view.view_config(410 context=Exception,411 permission=NO_PERMISSION_REQUIRED,412 renderer='json')413class Error(sw.allotmentclub.browser.base.View):414 title = u'Interner Server-Fehler'415 status_code = 500416 def __init__(self, context, request):417 super(Error, self).__init__(context, request)418 self.time = time.time()419 @property420 def show_traceback(self):421 return self.request.registry.settings['testing']422 @property423 def exc_line(self):424 return traceback.format_exception_only(425 type(self.context), self.context)[0].strip()426 @property427 def error_ref(self):428 return (str(int(self.time))[-4:] +429 str(int(hashlib.sha1(430 self.tb.encode('utf-8')).hexdigest(), 16))[:4])431 def update(self):432 self.tb = traceback.format_exc()433 self.error_summary = ('Ref: %s %s', self.error_ref, self.exc_line)434 self.request.response.status_code = self.status_code435 self.log()436 def log(self):437 error_log.error(*self.error_summary)438 for line in self.tb.splitlines():439 error_log.info(line, log_request=False)440 if self.show_traceback:441 print('Exception raised during test: {}'.format(self.tb))442@pyramid.view.view_config(443 route_name='sentry_frontend',444 permission=NO_PERMISSION_REQUIRED,445 renderer='json')446class SentryDataView(sw.allotmentclub.browser.base.View):447 def __call__(self):448 settings = pyramid.threadlocal.get_current_registry().settings449 return dict(dsn=settings.get('sentry.frontenddsn', ''))450@pyramid.view.view_config(451 context=pyramid.httpexceptions.HTTPException,452 permission=NO_PERMISSION_REQUIRED)453def HTTPException(context, request):454 return context455@pyramid.view.view_config(456 context=pyramid.httpexceptions.HTTPError,457 permission=NO_PERMISSION_REQUIRED,458 renderer='json')459class HTTPError(Error):460 @property461 def title(self):462 return '%s %s' % (self.status_code, type(self.context).__name__)463 @property464 def status_code(self):465 return self.context.status_code466@pyramid.view.notfound_view_config(renderer='json')467class NotFound(HTTPError):468 def log(self):469 if self.request.user.authenticated:470 log_with_user(471 auth_log.warning, self.request.user,472 'Ref: %s User %s tried to access non-existing url %s',473 self.error_ref, self.request.user.username, self.request.url)474 else:475 log_with_user(476 auth_log.warning, self.request.user,477 'Ref: %s Unauthenticated user tried to access '478 'non-existing url %s', self.error_ref, self.request.url)479@pyramid.events.subscriber(pyramid.events.ApplicationCreated)480def log_start(event):481 app_log.info('Application successfully started.')482shutdown_already_logged = False483def log_shutdown(signalnum, frame):484 global shutdown_already_logged485 if not shutdown_already_logged:486 shutdown_already_logged = True487 app_log.info('Application shutdown.')488 if signalnum == signal.SIGINT:489 # Make sure the port is closed, too:490 orig_sigint_handler(signalnum, frame)491signal.signal(signal.SIGTERM, log_shutdown)...
test_context_factory.py
Source:test_context_factory.py
...11 pass12class TestContextFactoryLocal(object):13 def test_lack_requirement(self):14 self.custom_context = dict(test='context')15 @hug.context_factory()16 def return_context(**kwargs):17 return self.custom_context18 @hug.delete_context()19 def delete_context(context, exception=None, errors=None, lacks_requirement=None):20 assert context == self.custom_context21 assert not exception22 assert not errors23 assert lacks_requirement24 assert isinstance(lacks_requirement, RequirementFailed)25 self.custom_context['launched_delete_context'] = True26 def test_local_requirement(**kwargs):27 assert 'context' in kwargs28 assert kwargs['context'] == self.custom_context29 self.custom_context['launched_requirement'] = True30 return RequirementFailed()31 @hug.local(requires=test_local_requirement)32 def requirement_local_function():33 self.custom_context['launched_local_function'] = True34 requirement_local_function()35 assert 'launched_local_function' not in self.custom_context36 assert 'launched_requirement' in self.custom_context37 assert 'launched_delete_context' in self.custom_context38 def test_directive(self):39 custom_context = dict(test='context')40 @hug.context_factory()41 def return_context(**kwargs):42 return custom_context43 @hug.delete_context()44 def delete_context(context, **kwargs):45 pass46 @hug.directive()47 def custom_directive(**kwargs):48 assert 'context' in kwargs49 assert kwargs['context'] == custom_context50 return 'custom'51 @hug.local()52 def directive_local_function(custom: custom_directive):53 assert custom == 'custom'54 directive_local_function()55 def test_validation(self):56 custom_context = dict(test='context', not_valid_number=43)57 @hug.context_factory()58 def return_context(**kwargs):59 return custom_context60 @hug.delete_context()61 def delete_context(context, exception=None, errors=None, lacks_requirement=None):62 assert context == custom_context63 assert not exception64 assert errors65 assert not lacks_requirement66 custom_context['launched_delete_context'] = True67 def test_requirement(**kwargs):68 assert 'context' in kwargs69 assert kwargs['context'] == custom_context70 custom_context['launched_requirement'] = True71 return RequirementFailed()72 @hug.type(extend=hug.types.number, accept_context=True)73 def custom_number_test(value, context):74 assert context == custom_context75 if value == context['not_valid_number']:76 raise ValueError('not valid number')77 return value78 @hug.local()79 def validation_local_function(value: custom_number_test):80 custom_context['launched_local_function'] = value81 validation_local_function(43)82 assert not 'launched_local_function' in custom_context83 assert 'launched_delete_context' in custom_context84 def test_transform(self):85 custom_context = dict(test='context', test_number=43)86 @hug.context_factory()87 def return_context(**kwargs):88 return custom_context89 @hug.delete_context()90 def delete_context(context, exception=None, errors=None, lacks_requirement=None):91 assert context == custom_context92 assert not exception93 assert not errors94 assert not lacks_requirement95 custom_context['launched_delete_context'] = True96 class UserSchema(Schema):97 name = fields.Str()98 @post_dump()99 def check_context(self, data):100 assert self.context['test'] == 'context'101 self.context['test_number'] += 1102 @hug.local()103 def validation_local_function() -> UserSchema():104 return {'name': 'test'}105 validation_local_function()106 assert 'test_number' in custom_context and custom_context['test_number'] == 44107 assert 'launched_delete_context' in custom_context108 def test_exception(self):109 custom_context = dict(test='context')110 @hug.context_factory()111 def return_context(**kwargs):112 return custom_context113 @hug.delete_context()114 def delete_context(context, exception=None, errors=None, lacks_requirement=None):115 assert context == custom_context116 assert exception117 assert isinstance(exception, CustomException)118 assert not errors119 assert not lacks_requirement120 custom_context['launched_delete_context'] = True121 @hug.local()122 def exception_local_function():123 custom_context['launched_local_function'] = True124 raise CustomException()125 with pytest.raises(CustomException):126 exception_local_function()127 assert 'launched_local_function' in custom_context128 assert 'launched_delete_context' in custom_context129 def test_success(self):130 custom_context = dict(test='context')131 @hug.context_factory()132 def return_context(**kwargs):133 return custom_context134 @hug.delete_context()135 def delete_context(context, exception=None, errors=None, lacks_requirement=None):136 assert context == custom_context137 assert not exception138 assert not errors139 assert not lacks_requirement140 custom_context['launched_delete_context'] = True141 @hug.local()142 def success_local_function():143 custom_context['launched_local_function'] = True144 success_local_function()145 assert 'launched_local_function' in custom_context146 assert 'launched_delete_context' in custom_context147class TestContextFactoryCLI(object):148 def test_lack_requirement(self):149 custom_context = dict(test='context')150 @hug.context_factory()151 def return_context(**kwargs):152 return custom_context153 @hug.delete_context()154 def delete_context(context, exception=None, errors=None, lacks_requirement=None):155 assert context == custom_context156 assert not exception157 assert not errors158 assert lacks_requirement159 assert isinstance(lacks_requirement, RequirementFailed)160 custom_context['launched_delete_context'] = True161 def test_requirement(**kwargs):162 assert 'context' in kwargs163 assert kwargs['context'] == custom_context164 custom_context['launched_requirement'] = True165 return RequirementFailed()166 @hug.cli(requires=test_requirement)167 def requirement_local_function():168 custom_context['launched_local_function'] = True169 hug.test.cli(requirement_local_function)170 assert 'launched_local_function' not in custom_context171 assert 'launched_requirement' in custom_context172 assert 'launched_delete_context' in custom_context173 def test_directive(self):174 custom_context = dict(test='context')175 @hug.context_factory()176 def return_context(**kwargs):177 return custom_context178 @hug.delete_context()179 def delete_context(context, **kwargs):180 pass181 @hug.directive()182 def custom_directive(**kwargs):183 assert 'context' in kwargs184 assert kwargs['context'] == custom_context185 return 'custom'186 @hug.cli()187 def directive_local_function(custom: custom_directive):188 assert custom == 'custom'189 hug.test.cli(directive_local_function)190 def test_validation(self):191 custom_context = dict(test='context', not_valid_number=43)192 @hug.context_factory()193 def return_context(**kwargs):194 return custom_context195 @hug.delete_context()196 def delete_context(context, exception=None, errors=None, lacks_requirement=None):197 assert not exception198 assert context == custom_context199 assert errors200 assert not lacks_requirement201 custom_context['launched_delete_context'] = True202 def test_requirement(**kwargs):203 assert 'context' in kwargs204 assert kwargs['context'] == custom_context205 custom_context['launched_requirement'] = True206 return RequirementFailed()207 @hug.type(extend=hug.types.number, accept_context=True)208 def new_custom_number_test(value, context):209 assert context == custom_context210 if value == context['not_valid_number']:211 raise ValueError('not valid number')212 return value213 @hug.cli()214 def validation_local_function(value: hug.types.number):215 custom_context['launched_local_function'] = value216 return 0217 with pytest.raises(SystemExit):218 hug.test.cli(validation_local_function, 'xxx')219 assert 'launched_local_function' not in custom_context220 assert 'launched_delete_context' in custom_context221 def test_transform(self):222 custom_context = dict(test='context', test_number=43)223 @hug.context_factory()224 def return_context(**kwargs):225 return custom_context226 @hug.delete_context()227 def delete_context(context, exception=None, errors=None, lacks_requirement=None):228 assert not exception229 assert context == custom_context230 assert not errors231 assert not lacks_requirement232 custom_context['launched_delete_context'] = True233 class UserSchema(Schema):234 name = fields.Str()235 @post_dump()236 def check_context(self, data):237 assert self.context['test'] == 'context'238 self.context['test_number'] += 1239 @hug.cli()240 def transform_cli_function() -> UserSchema():241 custom_context['launched_cli_function'] = True242 return {'name': 'test'}243 hug.test.cli(transform_cli_function)244 assert 'launched_cli_function' in custom_context245 assert 'launched_delete_context' in custom_context246 assert 'test_number' in custom_context247 assert custom_context['test_number'] == 44248 def test_exception(self):249 custom_context = dict(test='context')250 @hug.context_factory()251 def return_context(**kwargs):252 return custom_context253 @hug.delete_context()254 def delete_context(context, exception=None, errors=None, lacks_requirement=None):255 assert context == custom_context256 assert exception257 assert isinstance(exception, CustomException)258 assert not errors259 assert not lacks_requirement260 custom_context['launched_delete_context'] = True261 @hug.cli()262 def exception_local_function():263 custom_context['launched_local_function'] = True264 raise CustomException()265 hug.test.cli(exception_local_function)266 assert 'launched_local_function' in custom_context267 assert 'launched_delete_context' in custom_context268 def test_success(self):269 custom_context = dict(test='context')270 @hug.context_factory()271 def return_context(**kwargs):272 return custom_context273 @hug.delete_context()274 def delete_context(context, exception=None, errors=None, lacks_requirement=None):275 assert context == custom_context276 assert not exception277 assert not errors278 assert not lacks_requirement279 custom_context['launched_delete_context'] = True280 @hug.cli()281 def success_local_function():282 custom_context['launched_local_function'] = True283 hug.test.cli(success_local_function)284 assert 'launched_local_function' in custom_context285 assert 'launched_delete_context' in custom_context286class TestContextFactoryHTTP(object):287 def test_lack_requirement(self):288 custom_context = dict(test='context')289 @hug.context_factory()290 def return_context(**kwargs):291 return custom_context292 @hug.delete_context()293 def delete_context(context, exception=None, errors=None, lacks_requirement=None):294 assert context == custom_context295 assert not exception296 assert not errors297 assert lacks_requirement298 custom_context['launched_delete_context'] = True299 def test_requirement(**kwargs):300 assert 'context' in kwargs301 assert kwargs['context'] == custom_context302 custom_context['launched_requirement'] = True303 return 'requirement_failed'304 @hug.get('/requirement_function', requires=test_requirement)305 def requirement_http_function():306 custom_context['launched_local_function'] = True307 hug.test.get(module, '/requirement_function')308 assert 'launched_local_function' not in custom_context309 assert 'launched_requirement' in custom_context310 assert 'launched_delete_context' in custom_context311 def test_directive(self):312 custom_context = dict(test='context')313 @hug.context_factory()314 def return_context(**kwargs):315 return custom_context316 @hug.delete_context()317 def delete_context(context, **kwargs):318 pass319 @hug.directive()320 def custom_directive(**kwargs):321 assert 'context' in kwargs322 assert kwargs['context'] == custom_context323 return 'custom'324 @hug.get('/directive_function')325 def directive_http_function(custom: custom_directive):326 assert custom == 'custom'327 hug.test.get(module, '/directive_function')328 def test_validation(self):329 custom_context = dict(test='context', not_valid_number=43)330 @hug.context_factory()331 def return_context(**kwargs):332 return custom_context333 @hug.delete_context()334 def delete_context(context, exception=None, errors=None, lacks_requirement=None):335 assert context == custom_context336 assert not exception337 assert errors338 assert not lacks_requirement339 custom_context['launched_delete_context'] = True340 def test_requirement(**kwargs):341 assert 'context' in kwargs342 assert kwargs['context'] == custom_context343 custom_context['launched_requirement'] = True344 return RequirementFailed()345 @hug.type(extend=hug.types.number, accept_context=True)346 def custom_number_test(value, context):347 assert context == custom_context348 if value == context['not_valid_number']:349 raise ValueError('not valid number')350 return value351 @hug.get('/validation_function')352 def validation_http_function(value: custom_number_test):353 custom_context['launched_local_function'] = value354 hug.test.get(module, '/validation_function', 43)355 assert 'launched_local_function ' not in custom_context356 assert 'launched_delete_context' in custom_context357 def test_transform(self):358 custom_context = dict(test='context', test_number=43)359 @hug.context_factory()360 def return_context(**kwargs):361 return custom_context362 @hug.delete_context()363 def delete_context(context, exception=None, errors=None, lacks_requirement=None):364 assert context == custom_context365 assert not exception366 assert not errors367 assert not lacks_requirement368 custom_context['launched_delete_context'] = True369 class UserSchema(Schema):370 name = fields.Str()371 @post_dump()372 def check_context(self, data):373 assert self.context['test'] == 'context'374 self.context['test_number'] += 1375 @hug.get('/validation_function')376 def validation_http_function() -> UserSchema():377 custom_context['launched_local_function'] = True378 hug.test.get(module, '/validation_function', 43)379 assert 'launched_local_function' in custom_context380 assert 'launched_delete_context' in custom_context381 assert 'test_number' in custom_context382 assert custom_context['test_number'] == 44383 def test_exception(self):384 custom_context = dict(test='context')385 @hug.context_factory()386 def return_context(**kwargs):387 return custom_context388 @hug.delete_context()389 def delete_context(context, exception=None, errors=None, lacks_requirement=None):390 assert context == custom_context391 assert exception392 assert isinstance(exception, CustomException)393 assert not errors394 assert not lacks_requirement395 custom_context['launched_delete_context'] = True396 @hug.get('/exception_function')397 def exception_http_function():398 custom_context['launched_local_function'] = True399 raise CustomException()400 with pytest.raises(CustomException):401 hug.test.get(module, '/exception_function')402 assert 'launched_local_function' in custom_context403 assert 'launched_delete_context' in custom_context404 def test_success(self):405 custom_context = dict(test='context')406 @hug.context_factory()407 def return_context(**kwargs):408 return custom_context409 @hug.delete_context()410 def delete_context(context, exception=None, errors=None, lacks_requirement=None):411 assert context == custom_context412 assert not exception413 assert not errors414 assert not lacks_requirement415 custom_context['launched_delete_context'] = True416 @hug.get('/success_function')417 def success_http_function():418 custom_context['launched_local_function'] = True419 hug.test.get(module, '/success_function')420 assert 'launched_local_function' in custom_context...
TestMenuHandlers.py
Source:TestMenuHandlers.py
1import os2import unittest3from pathlib import Path4from unittest.mock import MagicMock5from unittest.mock import Mock6from pygame import font7from gameplay.keys import GameKeys8from gameplay.keys import KeyMapper9from screens.disposition_code import MenuAction10from screens.menu_handlers import KeySettingMenuContext11from screens.menu_handlers import MenuContextFactory12from screens.menu_handlers import MusicSelectionMenuContext13from screens.menu_handlers import OptionsMenuContext14from screens.menu_renderer import LazyTextRenderer15from screens.menu_renderer import StandardTextRenderer16# TODO: it probably makes sense to have separate test classes for each of the MenuContexts17class TestTopLevelMenu(unittest.TestCase):18 def setUp(self):19 self.jukebox = Mock()20 self.key_change_publisher = Mock()21 # I could mock these but the real thing is really just a wrapper around a dict, so whatever22 self.game_keys = GameKeys()23 self.key_mapper = KeyMapper(self.game_keys)24 # I could put a font file as a test resource but it's just as easy to change the test if I change the real fonts25 self.font_file = self._get_test_font_file()26 self.screen = MagicMock()27 font.init()28 self.context_factory = MenuContextFactory(29 self.jukebox, self.key_change_publisher, self.game_keys,30 self.key_mapper, self.font_file, self.screen)31 def test_toplevelmenu_labels(self):32 expected_labels = ["New Game", "High Scores", "Options", "Quit"]33 menu = self.context_factory.build_top_level_menu_screen(False)34 actual_labels = menu.get_render_info().get_labels()35 self.assertListEqual(expected_labels, actual_labels)36 def test_toplevelmenu_wrap_navigation(self):37 menu = self.context_factory.build_top_level_menu_screen(False)38 self.assertEqual(0, menu.get_selected_index())39 menu.move_to_previous_option()40 self.assertEqual(3, menu.get_selected_index())41 menu.move_to_next_option()42 self.assertEqual(0, menu.get_selected_index())43 def test_toplevelmenu_execute_play_game(self):44 menu = self.context_factory.build_top_level_menu_screen(False)45 self.assertEqual(0, menu.get_selected_index())46 next_state = menu.execute_current_option()47 self.assertEqual(menu, next_state.active_menu_screen)48 self.assertEqual(MenuAction.PLAY_GAME, next_state.get_menu_action())49 def test_toplevelmenu_execute_high_scores(self):50 menu = self.context_factory.build_top_level_menu_screen(False)51 self.move_down_n(1, menu)52 next_state = menu.execute_current_option()53 self.assertEqual(menu, next_state.active_menu_screen)54 self.assertEqual(MenuAction.SHOW_HIGH_SCORES, next_state.get_menu_action())55 def test_toplevelmenu_execute_options(self):56 menu = self.context_factory.build_top_level_menu_screen(False)57 self.move_down_n(2, menu)58 next_state = menu.execute_current_option()59 self.assertIsInstance(next_state.active_menu_screen, OptionsMenuContext)60 def test_toplevelmenu_execute_quit(self):61 menu = self.context_factory.build_top_level_menu_screen(False)62 self.move_down_n(3, menu)63 next_state = menu.execute_current_option()64 self.assertEqual(menu, next_state.active_menu_screen)65 self.assertEqual(MenuAction.QUIT, next_state.get_menu_action())66 def test_toplevelmenu_not_listening_for_key(self):67 menu = self.context_factory.build_top_level_menu_screen(False)68 self.assertFalse(menu.is_listening_for_key())69 def test_toplevelmenu_render_info(self):70 menu = self.context_factory.build_top_level_menu_screen(False)71 render_info = menu.get_render_info()72 self.assertIsInstance(render_info.get_text_renderer(), StandardTextRenderer)73 def test_pausedmenu_labels(self):74 expected_labels = ["Resume Game", "New Game", "High Scores", "Options", "Quit"]75 menu = self.context_factory.build_top_level_menu_screen(True)76 actual_labels = menu.get_render_info().get_labels()77 self.assertListEqual(expected_labels, actual_labels)78 def test_pausedmenu_execute_resume_game(self):79 menu = self.context_factory.build_top_level_menu_screen(True)80 self.assertEqual(0, menu.get_selected_index())81 next_state = menu.execute_current_option()82 self.assertEqual(menu, next_state.active_menu_screen)83 self.assertEqual(MenuAction.PLAY_GAME, next_state.get_menu_action())84 def test_pausedmenu_execute_new_game(self):85 menu = self.context_factory.build_top_level_menu_screen(True)86 self.move_down_n(1, menu)87 next_state = menu.execute_current_option()88 self.assertEqual(menu, next_state.active_menu_screen)89 self.assertEqual(MenuAction.NEW_GAME, next_state.get_menu_action())90 def test_paused_menu_execute_high_scores(self):91 menu = self.context_factory.build_top_level_menu_screen(True)92 self.move_down_n(2, menu)93 next_state = menu.execute_current_option()94 self.assertEqual(menu, next_state.active_menu_screen)95 self.assertEqual(MenuAction.SHOW_HIGH_SCORES, next_state.get_menu_action())96 def test_paused_menu_execute_options(self):97 menu = self.context_factory.build_top_level_menu_screen(True)98 self.move_down_n(3, menu)99 next_state = menu.execute_current_option()100 self.assertIsInstance(next_state.active_menu_screen, OptionsMenuContext)101 def test_paused_menu_execute_quit(self):102 menu = self.context_factory.build_top_level_menu_screen(True)103 self.move_down_n(4, menu)104 next_state = menu.execute_current_option()105 self.assertEqual(menu, next_state.active_menu_screen)106 self.assertEqual(MenuAction.QUIT, next_state.get_menu_action())107 def test_pausedmenu_not_listening_for_key(self):108 menu = self.context_factory.build_top_level_menu_screen(True)109 self.assertFalse(menu.is_listening_for_key())110 def test_pausedmenu_render_info(self):111 menu = self.context_factory.build_top_level_menu_screen(True)112 render_info = menu.get_render_info()113 self.assertIsInstance(render_info.get_text_renderer(), StandardTextRenderer)114 def test_musicmenu_labels(self):115 songlist = ["Song 1", "Song 2", "Song 3"]116 self.jukebox.get_available_song_titles.return_value = songlist117 menu = self.context_factory.build_music_selection_screen()118 self.assertEqual(songlist, menu.get_render_info().get_labels())119 def test_musicmenu_no_songs(self):120 self.jukebox.get_available_song_titles.return_value = []121 menu = self.context_factory.build_music_selection_screen()122 self.assertEqual([], menu.get_render_info().get_labels())123 self.assertEqual(0, menu.get_selected_index())124 menu.move_to_next_option()125 self.assertEqual(0, menu.get_selected_index())126 def test_musicmenu_execute_song_selection(self):127 songlist = ["Song 1", "Song 2", "Song 3"]128 self.jukebox.get_available_song_titles.return_value = songlist129 menu = self.context_factory.build_music_selection_screen()130 next_state = menu.execute_current_option()131 self.assertEqual(menu, next_state.get_active_menu_screen())132 self.assertEqual(MenuAction.MENU, next_state.get_menu_action())133 self.jukebox.set_song.assert_called_once_with("Song 1")134 def test_musicmenu_not_listening_for_key(self):135 self.jukebox.get_available_song_titles.return_value = ['Song 1']136 menu = self.context_factory.build_music_selection_screen()137 self.assertFalse(menu.is_listening_for_key())138 def test_musicmenu_render_info(self):139 self.jukebox.get_available_song_titles.return_value = []140 menu = self.context_factory.build_music_selection_screen()141 self.assertIsInstance(menu.get_render_info().get_text_renderer(), StandardTextRenderer)142 def test_keymenu_default_labels(self):143 menu = self.context_factory.build_key_changing_screen()144 actual_labels = menu.get_labels()145 expected_labels = self.default_key_labels()146 self.assertEqual(expected_labels, actual_labels)147 def test_keymenu_not_listening_for_key_by_default(self):148 menu = self.context_factory.build_key_changing_screen()149 self.assertFalse(menu.is_listening_for_key())150 def test_keymenu_listening_for_key_after_executing_option(self):151 menu = self.context_factory.build_key_changing_screen()152 menu.execute_current_option()153 self.assertTrue(menu.is_listening_for_key())154 def test_keymenu_update_left_key(self):155 self._run_key_update_test(0, 'J', 'Move Left: <J>')156 def test_keymenu_update_right_key(self):157 self._run_key_update_test(1, 'L', 'Move Right: <L>')158 def test_keymenu_update_down_key(self):159 self._run_key_update_test(2, 'K', 'Move Down: <K>')160 def test_keymenu_update_drop_key(self):161 self._run_key_update_test(3, 'Up', 'Drop Piece: <Up>')162 def test_keymenu_update_rot_left_key(self):163 self._run_key_update_test(4, 'One', 'Rotate Left: <One>')164 def test_keymenu_update_rot_right_key(self):165 self._run_key_update_test(5, 'Two', 'Rotate Right: <Two>')166 def _run_key_update_test(self, label_index, new_key_name, expected_label):167 menu = self.context_factory.build_key_changing_screen()168 self.move_down_n(label_index, menu)169 menu.execute_current_option()170 menu.handle_key_event(self.game_keys.by_name(new_key_name))171 expected_labels = self.default_key_labels()172 expected_labels[label_index] = expected_label173 self.assertEqual(expected_labels, menu.get_labels())174 def test_keymenu_updated_labels_are_cached(self):175 menu = self.context_factory.build_key_changing_screen()176 self.assertIs(menu.get_labels(), menu.get_labels(), 'Original labels should have been cached')177 menu.execute_current_option()178 menu.handle_key_event(self.game_keys.by_name('A'))179 self.assertIs(menu.get_labels(), menu.get_labels(), 'New labels should have been cached')180 def test_keymenu_render_info(self):181 menu = self.context_factory.build_key_changing_screen()182 self.assertEqual(self.default_key_labels(), menu.get_render_info().get_labels())183 self.assertIsInstance(menu.get_render_info().get_text_renderer(), LazyTextRenderer)184 def test_optionsmenu_default_labels(self):185 menu = self.context_factory.build_options_screen()186 self.assertEqual(self.default_options_labels(), menu.get_labels())187 def test_optionsmenu_sound_off_label(self):188 menu = self.context_factory.build_options_screen()189 menu.execute_current_option()190 expected = self.default_options_labels()191 expected[0] = "Sound On / [Off]"192 self.assertEqual(expected, menu.get_labels())193 def test_optionsmenu_music_off_label(self):194 menu = self.context_factory.build_options_screen()195 self.move_down_n(1, menu)196 menu.execute_current_option()197 expected = self.default_options_labels()198 expected[1] = "Music On / [Off]"199 self.assertEqual(expected, menu.get_labels())200 def test_optionsmenu_toggle_sound_twice(self):201 menu = self.context_factory.build_options_screen()202 menu.execute_current_option()203 menu.execute_current_option()204 self.assertEqual(self.default_options_labels(), menu.get_labels())205 def test_optionsmenu_toggle_music_twice(self):206 menu = self.context_factory.build_options_screen()207 self.move_down_n(1, menu)208 menu.execute_current_option()209 menu.execute_current_option()210 self.assertEqual(self.default_options_labels(), menu.get_labels())211 def test_optionsmenu_sound_toggle_returns_menu_state(self):212 menu = self.context_factory.build_options_screen()213 next_state = menu.execute_current_option()214 self.assertEqual(MenuAction.MENU, next_state.get_menu_action())215 self.assertEqual(menu, next_state.get_active_menu_screen())216 def test_optionsmenu_sound_toggle_affects_jukebox(self):217 menu = self.context_factory.build_options_screen()218 menu.execute_current_option()219 self.jukebox.enable_sound.assert_called_once_with(False)220 self.jukebox.reset_mock()221 menu.execute_current_option()222 self.jukebox.enable_sound.assert_called_once_with(True)223 def test_optionsmenu_music_toggle_returns_menu_state(self):224 menu = self.context_factory.build_options_screen()225 self.move_down_n(1, menu)226 next_state = menu.execute_current_option()227 self.assertEqual(MenuAction.MENU, next_state.get_menu_action())228 self.assertEqual(menu, next_state.get_active_menu_screen())229 def test_optionsmenu_music_toggle_affects_jukebox(self):230 menu = self.context_factory.build_options_screen()231 self.move_down_n(1, menu)232 menu.execute_current_option()233 self.jukebox.enable_music.assert_called_once_with(False)234 self.jukebox.reset_mock()235 menu.execute_current_option()236 self.jukebox.enable_music.assert_called_once_with(True)237 def test_optionsmenu_navigate_to_music_selection(self):238 self.jukebox.get_available_song_titles.return_value = ["Song 1"]239 menu = self.context_factory.build_options_screen()240 self.move_down_n(2, menu)241 next_state = menu.execute_current_option()242 self.assertEqual(MenuAction.MENU, next_state.get_menu_action())243 returned_submenu = next_state.get_active_menu_screen()244 self.assertIsInstance(returned_submenu, MusicSelectionMenuContext)245 self.assertEqual(["Song 1"], returned_submenu.get_render_info().get_labels(),246 "Options menu should have passed the correct songs to the submenu")247 def test_optionsmenu_navigate_to_key_menu(self):248 menu = self.context_factory.build_options_screen()249 self.move_down_n(3, menu)250 next_state = menu.execute_current_option()251 self.assertEqual(MenuAction.MENU, next_state.get_menu_action())252 returned_submenu = next_state.get_active_menu_screen()253 self.assertIsInstance(returned_submenu, KeySettingMenuContext)254 @staticmethod255 def move_down_n(n, menu):256 """ A helper for setting the menu selection index """257 for _ in range(n):258 menu.move_to_next_option()259 @staticmethod260 def default_key_labels():261 return [262 'Move Left: <Left>',263 'Move Right: <Right>',264 'Move Down: <Down>',265 'Drop Piece: <Space>',266 'Rotate Left: <Z>',267 'Rotate Right: <X>'268 ]269 @staticmethod270 def default_options_labels():271 return [272 "Sound [On] / Off",273 "Music [On] / Off",274 "Change Song",275 "Change Keys"]276 @staticmethod277 def _get_test_font_file():278 current_path = Path(os.path.dirname(os.path.realpath(__file__)))...
test_case_test.py
Source:test_case_test.py
...16from checkers import asserts17from checkers import test_case18from checkers import test_result19from checkers.runners import pyunit20def _dummy_context_factory(tc):21 return checkers.Context(tc, None)22@checkers.test23def _dummy_test():24 pass25@checkers.test26def test_test_case_init():27 test = _dummy_test28 context_factory = _dummy_context_factory29 tc = test_case.TestCase(test, context_factory)30 asserts.are_equal(tc.name, '_dummy_test')31 asserts.are_equal(tc.full_name, 'test_case_test._dummy_test')32 asserts.are_same(tc.test, test)33 asserts.is_not_none(tc.context)34 asserts.is_empty(tc.description)...
LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!