2026-10-22 07:35:02 +00:00
#!/usr/bin/env python3
""" Regresión de selectores mediante el MCP/Chromium del preparador sintético existente.
METIS_PLAYWRIGHT_STORAGE_STATE = . artifacts / playwright / authenticated - ID / session . json \
python3 scripts / picker - ui . test . py
No inicia servicios ni escribe entidades . La fixture React solo se importa por Vite en tests .
"""
import contextlib
import json
import os
from pathlib import Path
import runpy
import tomllib
import unittest
from urllib . parse import quote , urlparse
ROOT = Path ( __file__ ) . resolve ( ) . parent . parent
AUDIT = runpy . run_path ( str ( ROOT / " scripts/visual-audit.py " ) )
SIZES = [ ( 320 , 640 ) , ( 390 , 844 ) , ( 768 , 1024 ) , ( 1024 , 768 ) , ( 1366 , 768 ) , ( 1440 , 900 ) ]
class PickerUiTests ( unittest . TestCase ) :
@classmethod
def setUpClass ( cls ) :
value = os . environ . get ( " METIS_PLAYWRIGHT_STORAGE_STATE " )
if not value :
raise unittest . SkipTest ( " Requiere sesión sintética activa de scripts/visual-audit.py. " )
state = ( ROOT / value ) . resolve ( )
if not state . is_relative_to ( ROOT / " .artifacts/playwright " ) or state . stat ( ) . st_mode & 0o077 :
raise RuntimeError ( " La sesión debe ser privada y pertenecer al directorio de auditoría. " )
report = json . loads ( ( state . parent / " report.json " ) . read_text ( ) )
if urlparse ( report [ ' origin ' ] ) . hostname != ' localhost ' or report . get ( ' ordinaryUser ' ) is not True :
raise RuntimeError ( " Se requiere una sesión sintética local ordinaria. " )
cls . origin = report [ ' origin ' ]
cls . company = ' /companies/ ' + report [ ' companyId ' ]
cls . asset_id = report [ ' assetId ' ]
cls . output = ROOT / ' .artifacts/playwright/l02-1 '
cls . output . mkdir ( parents = True , exist_ok = True )
cls . results = [ ]
cls . measurements = cls . output / ( ' measurements- ' + str ( os . getpid ( ) ) + ' .json ' )
cls . stack = contextlib . ExitStack ( )
cls . addClassCleanup ( cls . stack . close )
config = tomllib . loads ( ( ROOT / ' .codex/config.toml ' ) . read_text ( ) ) [ ' mcp_servers ' ] [ ' playwright ' ]
cls . client = cls . stack . enter_context ( AUDIT [ ' browser ' ] ( config , state ) )
status = cls . code ( ' return (await page.request.get( ' + json . dumps ( cls . origin + ' /api/companies ' ) + ' )).status(); ' )
if status != 200 :
raise RuntimeError ( ' La sesión sintética no está activa; preparar otra según docs/playwright-mcp.md. ' )
cls . code ( " page.blockedWrites=[];await page.route( ' **/api/** ' ,r=> { if([ ' GET ' , ' HEAD ' , ' OPTIONS ' ].includes(r.request().method()))return r.continue();page.blockedWrites.push(r.request().method());return r.abort()});return true; " )
@classmethod
def tool ( cls , name , args ) :
return AUDIT [ ' tool ' ] ( cls . client , name , args )
@classmethod
def code ( cls , body ) :
return AUDIT [ ' result_json ' ] ( cls . tool ( ' browser_run_code_unsafe ' , { ' code ' : ' async(page)=> { ' + body + ' } ' } ) )
@classmethod
def tearDownClass ( cls ) :
( cls . output / ' console.json ' ) . write_text ( json . dumps ( cls . tool ( ' browser_console_messages ' , { ' level ' : ' error ' } ) , ensure_ascii = False , indent = 2 ) + ' \n ' )
( cls . output / ' blocked-writes.json ' ) . write_text ( json . dumps ( cls . code ( ' return page.blockedWrites; ' ) ) + ' \n ' )
def settle ( self ) :
self . code ( " await page.evaluate(async()=> { await document.fonts.ready;await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));await Promise.all(document.getAnimations().filter(a=>a.effect?.getTiming().iterations!==Infinity).map(a=>a.finished.catch(()=> {} )))});return true; " )
def resize ( self , w , h ) :
self . tool ( ' browser_resize ' , { ' width ' : w , ' height ' : h } )
self . settle ( )
def navigate ( self , path ) :
self . code ( " await page.evaluate(()=>window.pickerTestRoot?.unmount());return true; " )
self . tool ( ' browser_navigate ' , { ' url ' : self . origin + path } )
self . code ( " await page.locator( ' [data-slot=workspace] ' ).waitFor();return true; " )
self . settle ( )
def record ( self , name , value ) :
self . results . append ( { ' scenario ' : name , ' value ' : value } )
self . measurements . write_text ( json . dumps ( self . results , ensure_ascii = False , indent = 2 ) + ' \n ' )
def capture ( self , name ) :
AUDIT [ ' capture ' ] ( self . client , self . output , name )
result = self . tool ( ' browser_snapshot ' , { } )
( self . output / ( name + ' .txt ' ) ) . write_text ( ' \n ' . join ( v [ ' text ' ] for v in result [ ' content ' ] if v . get ( ' type ' ) == ' text ' ) )
def fixture ( self , count = 12 , locale = ' es ' , long_names = False , kind = ' fields ' ) :
self . navigate ( ' /companies ' )
module = ' /@fs/ ' + quote ( str ( ROOT / ' packages/rdx-ui/src/picker-browser-fixture.tsx ' ) , safe = ' / ' )
options = { ' count ' : count , ' locale ' : locale , ' longNames ' : long_names , ' kind ' : kind }
self . code ( ' const target=await page.evaluateHandle(()=> { const d=document.createElement( " div " );document.body.append(d);return d});await page.evaluate(async( { target,options,module})=> { const {mountPickerFixture} =await import(module);window.pickerTestRoot=mountPickerFixture(target,options)}, { target,options: ' + json . dumps ( options ) + ' ,module: ' + json . dumps ( module ) + ' });return true; ' )
self . code ( " await page.getByRole( ' button ' , { name: " + json . dumps ( ' Choose fields ' if locale == ' en ' else ' Seleccionar campos ' ) + " ,exact:true}).click();await page.getByRole( ' dialog ' ).locator( ' input[data-quick-search] ' ).waitFor();return true; " )
self . settle ( )
def geometry ( self ) :
return self . code ( """ return await page.locator( ' [role=dialog]:has([data-slot=form-body]) ' ).last().evaluate(d=> {
let c = d . querySelector ( ' [data-slot=table-container] ' ) ; for ( let n = c . parentElement ; n & & n != = d ; n = n . parentElement ) { if ( getComputedStyle ( n ) . overflowY == = ' auto ' & & n . clientHeight < c . clientHeight ) c = n } const b = c . getBoundingClientRect ( ) , head = d . querySelector ( ' thead ' ) . getBoundingClientRect ( ) ;
const body = d . querySelector ( ' [data-slot=form-body] ' ) , br = body . getBoundingClientRect ( ) , f = d . querySelector ( ' [data-slot=dialog-footer] ' ) . getBoundingClientRect ( ) , dr = d . getBoundingClientRect ( ) ;
const bottom = Math . min ( b . bottom , br . bottom , f . top ) , top = Math . max ( b . top , br . top , head . bottom ) ;
const rows = [ . . . d . querySelectorAll ( ' tbody tr ' ) ] . filter ( e = > e . querySelector ( ' [role=checkbox],[role=radio] ' ) ) ;
return { viewport : [ innerWidth , innerHeight ] , height : dr . height , tableClient : c . clientHeight , tableScroll : c . scrollHeight , bodyClient : body . clientHeight , bodyScroll : body . scrollHeight , full : rows . filter ( e = > { const r = e . getBoundingClientRect ( ) ; return r . top > = top - .5 & & r . bottom < = bottom + .5 } ) . length , rows : rows . length , footerVisible : f . top > = dr . top & & f . bottom < = dr . bottom + .5 , focusQuick : document . activeElement . hasAttribute ( ' data-quick-search ' ) , overflow : document . documentElement . scrollWidth > innerWidth } ;
} ) ; """ )
def test_01_capacity_counts_and_resize ( self ) :
for count in [ 0 , 1 , 2 , 12 ] :
with self . subTest ( count = count ) :
self . resize ( 320 , 640 )
self . fixture ( count = count )
g = self . geometry ( ) ; self . record ( ' count- ' + str ( count ) , g )
self . assertGreater ( g [ ' tableClient ' ] , 0 )
self . assertTrue ( g [ ' footerVisible ' ] )
self . assertTrue ( g [ ' focusQuick ' ] )
self . assertFalse ( g [ ' overflow ' ] )
if count < 3 : self . assertEqual ( g [ ' full ' ] , count )
if count > = 2 : self . assertGreaterEqual ( g [ ' full ' ] , 2 )
self . code ( " await page.keyboard.press( ' Escape ' );return true; " )
for kind in [ ' fields ' , ' options ' ] :
self . fixture ( kind = kind )
for w , h in SIZES :
self . resize ( w , h ) ; g = self . geometry ( ) ; self . record ( f ' { kind } - { w } ' , g )
self . assertGreater ( g [ ' tableClient ' ] , 0 )
self . assertGreaterEqual ( g [ ' full ' ] , 2 )
self . assertTrue ( g [ ' footerVisible ' ] )
self . assertFalse ( g [ ' overflow ' ] )
if w in [ 320 , 1440 ] : self . capture ( f ' after-fixture- { kind } - { w } ' )
self . code ( " await page.keyboard.press( ' Escape ' );return true; " )
def test_02_long_help_and_short_window ( self ) :
self . resize ( 320 , 640 ) ; self . fixture ( long_names = True )
g = self . geometry ( ) ; self . record ( ' long-names ' , g ) ; self . assertGreater ( g [ ' tableClient ' ] , 0 )
self . code ( " await page.getByRole( ' dialog ' ).locator( ' [data-slot=table-container] ' ).evaluate(e=>e.scrollTop=e.scrollHeight);return true; " )
self . settle ( ) ; self . capture ( ' after-long-names-scrolled ' )
self . code ( " await page.getByRole( ' button ' , { name: ' Sección y ayuda ' ,exact:true}).click();await page.locator( ' [data-slot=popover-content] ' ).waitFor();return true; " )
self . settle ( )
help_metrics = self . code ( " return await page.locator( ' [data-slot=popover-content] ' ).evaluate(e=>( { client:e.clientHeight,scroll:e.scrollHeight,top:e.getBoundingClientRect().top,bottom:e.getBoundingClientRect().bottom,text:e.textContent})); " )
self . record ( ' long-help ' , help_metrics ) ; self . assertGreater ( help_metrics [ ' client ' ] , 0 )
self . assertGreater ( help_metrics [ ' scroll ' ] , help_metrics [ ' client ' ] )
self . assertIn ( ' Sección de destino ' , help_metrics [ ' text ' ] )
self . code ( " await page.keyboard.press( ' PageDown ' );await page.waitForFunction(()=>document.querySelector( ' [data-slot=popover-content] ' ).scrollTop>0);return true; " )
self . record ( ' help-keyboard-scroll ' , self . code ( " return await page.locator( ' [data-slot=popover-content] ' ).evaluate(e=>( { scroll:e.scrollTop,focused:e.contains(document.activeElement)})); " ) )
self . assertEqual ( self . geometry ( ) [ ' height ' ] , g [ ' height ' ] )
self . capture ( ' after-help-320 ' )
self . code ( " await page.keyboard.press( ' Escape ' );await page.locator( ' [data-slot=popover-content] ' ).waitFor( { state: ' hidden ' });return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).count(); " ) , 1 )
self . resize ( 320 , 480 ) ; self . record ( ' short-window ' , self . geometry ( ) ) ; self . assertGreater ( self . geometry ( ) [ ' tableClient ' ] , 0 )
self . capture ( ' after-short-window ' )
self . code ( " await page.keyboard.press( ' Escape ' );return true; " )
def test_03_identity_draft_keyboard_and_paging ( self ) :
self . resize ( 390 , 844 ) ; self . fixture ( kind = ' options ' )
self . code ( " await page.getByRole( ' dialog ' ).getByRole( ' button ' , { name: ' Columnas ' ,exact:true}).click();return true; " )
states = self . code ( " return await page.getByRole( ' dialog ' ).last().getByRole( ' checkbox ' ).evaluateAll(es=>es.map(e=>( { label:e.closest( ' label ' ).textContent,disabled:e.getAttribute( ' aria-disabled ' )=== ' true ' ||e.hasAttribute( ' disabled ' )}))); " )
self . record ( ' protected-columns ' , states )
2026-10-22 09:16:59 +00:00
self . assertTrue ( self . code ( " return page.getByRole( ' dialog ' ).last().getByRole( ' checkbox ' , { name: ' Campo ' ,exact:true}).isDisabled(); " ) )
# L02.2 excluye el control interno selection del configurador; sigue presente en la tabla.
self . assertFalse ( any ( x [ ' label ' ] == ' Selección ' for x in states ) )
2026-10-22 07:35:02 +00:00
self . code ( " await page.getByRole( ' dialog ' ).last().getByRole( ' checkbox ' , { name: ' Detalles ' ,exact:true}).uncheck();await page.keyboard.press( ' Escape ' );await page.getByRole( ' dialog ' ).getByRole( ' radio ' ).first().focus();await page.keyboard.press( ' Space ' );return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).getByRole( ' radio ' ).first().getAttribute( ' aria-checked ' ); " ) , ' true ' )
self . assertEqual ( self . code ( " return await page.locator( ' output ' ).textContent(); " ) , ' — ' )
self . capture ( ' after-option-identity ' )
self . code ( " await page.getByRole( ' button ' , { name: ' Cancelar ' ,exact:true}).click();return true; " )
self . assertTrue ( self . code ( " return await page.getByRole( ' button ' , { name: ' Seleccionar campos ' ,exact:true}).evaluate(e=>e===document.activeElement); " ) )
self . code ( " await page.getByRole( ' button ' , { name: ' Seleccionar campos ' ,exact:true}).click();return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).getByRole( ' radio ' ).first().getAttribute( ' aria-checked ' ); " ) , ' false ' )
self . code ( " await page.getByRole( ' dialog ' ).getByRole( ' radio ' ).first().check();await page.getByRole( ' dialog ' ).getByRole( ' button ' , { name: ' Página siguiente ' ,exact:true}).click();return true; " )
self . assertEqual ( self . geometry ( ) [ ' rows ' ] , 2 )
self . code ( " await page.getByRole( ' dialog ' ).getByRole( ' button ' , { name: ' Seleccionar ' ,exact:true}).click();return true; " )
self . assertEqual ( self . code ( " return await page.locator( ' output ' ).textContent(); " ) , ' option-0 ' )
def test_04_catalogs_actual_nested_and_empty ( self ) :
for module in [ ' assets ' , ' resources ' ] :
self . resize ( 320 , 640 ) ; self . navigate ( ' /catalogs/ ' + module )
self . code ( " await page.getByRole( ' button ' , { name: ' Nueva plantilla ' ,exact:true}).click();await page.getByRole( ' dialog ' ).getByRole( ' button ' , { name:/^Añadir campo/}).click();return true; " )
self . settle ( ) ; initial = self . geometry ( ) ; self . record ( ' catalog- ' + module , initial )
self . assertGreater ( initial [ ' tableClient ' ] , 0 ) ; self . assertTrue ( initial [ ' focusQuick ' ] )
self . capture ( ' after-catalog- ' + module + ' -320 ' )
self . code ( " await page.getByRole( ' dialog ' ).last().getByRole( ' button ' , { name: ' Nuevo campo ' ,exact:true}).click();return true; " )
self . settle ( ) ; self . capture ( ' after-quick-create- ' + module ) ; self . assertEqual ( self . code ( " return await page.locator( ' [role=dialog][data-slot=dialog-content] ' ).count(); " ) , 3 )
self . code ( " await page.keyboard.press( ' Escape ' );await page.locator( ' [role=dialog] input[data-quick-search] ' ).waitFor( { state: ' visible ' });return true; " )
self . settle ( ) ; self . capture ( ' after-quick-return- ' + module )
self . assertEqual ( self . code ( " return await page.locator( ' [role=dialog][data-slot=dialog-content] ' ).count(); " ) , 2 )
self . assertTrue ( self . code ( " return await page.getByRole( ' button ' , { name: ' Nuevo campo ' ,exact:true}).evaluate(e=>e===document.activeElement); " ) )
self . code ( " await page.getByRole( ' dialog ' ).last().locator( ' input[data-quick-search] ' ).fill( ' NO-EXISTE-L02 ' );return true; " )
self . settle ( ) ; self . assertEqual ( self . geometry ( ) [ ' height ' ] , initial [ ' height ' ] )
self . assertEqual ( self . geometry ( ) [ ' rows ' ] , 0 )
self . code ( " await page.keyboard.press( ' Escape ' );return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).count(); " ) , 1 )
self . assertTrue ( self . code ( " return await page.evaluate(()=>document.activeElement.tagName=== ' BUTTON ' ); " ) )
def test_05_english ( self ) :
self . resize ( 320 , 640 ) ; self . fixture ( locale = ' en ' , kind = ' options ' , count = 1 )
text = self . code ( " return await page.getByRole( ' dialog ' ).locator( ' p[aria-live] ' ).innerText(); " )
self . assertEqual ( text , ' 1– 1 of 1 field ' )
self . assertNotIn ( ' Choose ' , text )
self . record ( ' english-range ' , text ) ; self . capture ( ' after-english-320 ' )
def test_06_remote ( self ) :
self . resize ( 320 , 640 ) ; self . fixture ( kind = ' remote ' )
self . assertGreater ( self . geometry ( ) [ ' tableClient ' ] , 0 )
self . code ( " await page.getByRole( ' dialog ' ).getByRole( ' radio ' ).first().check();await page.getByRole( ' button ' , { name: ' Siguiente ' ,exact:true}).click();return true; " )
self . assertEqual ( self . geometry ( ) [ ' rows ' ] , 2 )
self . capture ( ' after-remote-320 ' ) ; self . record ( ' remote ' , self . geometry ( ) )
self . code ( " await page.getByRole( ' button ' , { name: ' Cancelar ' ,exact:true}).click();return true; " )
self . assertEqual ( self . code ( ' return await page.locator( \' output[aria-label= " Resultado " ] \' ).textContent(); ' ) , ' — ' )
def test_07_actual_assets_profile_and_domain ( self ) :
self . resize ( 1440 , 900 )
self . code ( """ const companyId= """ + json . dumps ( self . company . split ( ' / ' ) [ - 1 ] ) + """ ;
const siteId = ' 00000000-0000-7000-8000-000000000201 ' ;
await page . route ( ' **/api/companies/ ' + companyId , async r = > { const response = await r . fetch ( ) ; const data = await response . json ( ) ; data . company . sites = [ { id : siteId , companyId , name : ' L02 Sede sintética ' , active : true , address : null , phone : null } ] ; await r . fulfill ( { response , json : data } ) } ) ;
await page . route ( ' **/api/assets/ ' + companyId , async r = > { const response = await r . fetch ( ) ; const data = await response . json ( ) ; const base = data . assets [ 0 ] ; data . assets = Array . from ( { length : 12 } , ( _ , i ) = > { const a = structuredClone ( base ) ; if ( i ) a . id = ' 00000000-0000-7000-8000- ' + String ( 300 + i ) . padStart ( 12 , ' 0 ' ) ; a . values . siteId = siteId ; a . values . name = ' L02 Equipo ' + String ( i + 1 ) . padStart ( 2 , ' 0 ' ) ; a . assigned = i == = 0 ; a . displayStatus = i == = 0 ? ' in_use ' : ' available ' ; a . history = [ { . . . a . history [ 0 ] , id : ' 00000000-0000-7000-8000- ' + String ( 500 + i ) . padStart ( 12 , ' 0 ' ) , values : structuredClone ( a . values ) } ] ; return a } ) ; const values = { name : ' L02 Puesto ' , active : true , siteId , personId : null , computerAssetId : base . id , assetIds : [ base . id ] } ; data . workstations = [ { id : ' 00000000-0000-7000-8000-000000000600 ' , companyId , values , history : [ { . . . base . history [ 0 ] , id : ' 00000000-0000-7000-8000-000000000601 ' , values } ] } ] ; await r . fulfill ( { response , json : data } ) } ) ; return true ;
""" )
self . navigate ( self . company + ' /workstations ' )
self . code ( " await page.getByRole( ' button ' , { name: ' Nuevo puesto ' ,exact:true}).click();await page.getByRole( ' dialog ' ).getByRole( ' button ' , { name: ' Siguiente ' ,exact:true}).click();return true; " )
label = self . code ( " return await page.getByRole( ' button ' , { name: ' Seleccionar activos ' ,exact:true}).evaluate(e=>( { text:e.textContent,override:e.getAttribute( ' aria-label ' )})); " )
self . assertIsNone ( label [ ' override ' ] ) ; self . record ( ' actual-asset-label ' , label )
self . code ( " await page.getByRole( ' button ' , { name: ' Seleccionar activos ' ,exact:true}).click();return true; " )
self . settle ( )
for w , h in SIZES :
self . resize ( w , h ) ; g = self . geometry ( ) ; self . record ( ' actual-assets- ' + str ( w ) , g )
if w == 320 : self . assertGreaterEqual ( g [ ' full ' ] , 2 )
self . assertGreater ( g [ ' tableClient ' ] , 0 ) ; self . assertTrue ( g [ ' footerVisible ' ] ) ; self . assertFalse ( g [ ' overflow ' ] )
if w == 320 : self . capture ( ' after-actual-assets-320 ' )
self . code ( " await page.getByRole( ' dialog ' ).last().locator( ' [role=checkbox]:not([aria-disabled=true]) ' ).first().check();await page.getByRole( ' dialog ' ).last().getByRole( ' button ' , { name: ' Cancelar ' ,exact:true}).click();return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).count(); " ) , 1 )
self . code ( " await page.getByRole( ' button ' , { name: ' Seleccionar activos ' ,exact:true}).click();return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).last().locator( ' [role=checkbox][aria-checked=true] ' ).count(); " ) , 0 )
self . code ( " await page.getByRole( ' dialog ' ).last().locator( ' [role=checkbox]:not([aria-disabled=true]) ' ).first().check();await page.getByRole( ' dialog ' ).last().locator( ' [data-slot=dialog-footer] button ' ).last().click();return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).count(); " ) , 1 )
self . code ( " await page.keyboard.press( ' Escape ' );return true; " )
self . resize ( 1440 , 900 ) ; self . navigate ( self . company + ' /work-profiles ' )
self . code ( " await page.getByRole( ' button ' , { name: ' Nuevo perfil ' ,exact:true}).click();await page.getByRole( ' dialog ' ).getByRole( ' button ' , { name: ' Seleccionar puesto ' ,exact:true}).click();return true; " )
self . settle ( ) ; text = self . code ( " return await page.getByRole( ' dialog ' ).last().locator( ' p[aria-live] ' ).innerText(); " )
self . assertEqual ( text , ' 1– 1 de 1 puesto ' ) ; self . record ( ' actual-workstation-range ' , text ) ; self . capture ( ' after-actual-workstation ' )
self . code ( " await page.keyboard.press( ' Escape ' );await page.keyboard.press( ' Escape ' );return true; " )
self . navigate ( self . company + ' /assets/ ' + self . asset_id )
self . code ( " await page.getByRole( ' button ' , { name: ' Asociar a dominio ' ,exact:true}).click();await page.getByRole( ' dialog ' ).locator( ' button[id$= \" -domain \" ] ' ).click();return true; " )
self . settle ( ) ; self . resize ( 320 , 640 ) ; self . capture ( ' after-actual-domain-320 ' )
self . assertTrue ( self . code ( " return await page.getByRole( ' dialog ' ).last().locator( ' input[data-quick-search] ' ).evaluate(e=>e===document.activeElement); " ) )
self . code ( " await page.keyboard.press( ' Escape ' );return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).count(); " ) , 1 )
def test_08_checkboxes_and_effective_text_zoom ( self ) :
self . resize ( 390 , 844 ) ; self . fixture ( )
self . code ( " await page.getByRole( ' dialog ' ).locator( ' tbody tr ' ).first().locator( ' td ' ).nth(1).click();return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).getByRole( ' checkbox ' ).first().getAttribute( ' aria-checked ' ); " ) , ' true ' )
self . code ( " await page.getByRole( ' dialog ' ).getByRole( ' checkbox ' ).first().uncheck();return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).getByRole( ' checkbox ' ).first().getAttribute( ' aria-checked ' ); " ) , ' false ' )
self . code ( " await page.getByRole( ' dialog ' ).locator( ' [data-slot=table-container] ' ).evaluate(e=>e.scrollTop=e.scrollHeight);return true; " )
self . assertEqual ( self . code ( " return await page.getByRole( ' dialog ' ).getByRole( ' checkbox ' ).nth(9).getAttribute( ' aria-disabled ' ); " ) , ' true ' )
self . code ( " await page.getByRole( ' dialog ' ).getByRole( ' button ' , { name: ' Página siguiente ' ,exact:true}).click();return true; " )
self . assertEqual ( self . geometry ( ) [ ' rows ' ] , 2 )
self . code ( " await page.evaluate(()=>document.documentElement.style.fontSize= ' 200 % ' );return true; " )
self . settle ( ) ; g = self . geometry ( ) ; self . record ( ' text-scale-200 ' , g )
self . assertGreater ( g [ ' tableClient ' ] , 0 ) ; self . assertGreater ( g [ ' bodyClient ' ] , 0 )
self . assertFalse ( g [ ' overflow ' ] )
self . code ( " await page.getByRole( ' dialog ' ).getByRole( ' checkbox ' ).first().focus();return true; " )
self . settle ( )
focused = self . code ( " return await page.getByRole( ' dialog ' ).getByRole( ' checkbox ' ).first().evaluate(e=> { const r=e.getBoundingClientRect(),b=e.closest( ' [data-slot=form-body] ' ).getBoundingClientRect();return { top:r.top,bottom:r.bottom,bodyTop:b.top,bodyBottom:b.bottom}}); " )
self . record ( ' text-scale-focus ' , focused )
self . assertGreaterEqual ( focused [ ' top ' ] , focused [ ' bodyTop ' ] )
self . assertLessEqual ( focused [ ' bottom ' ] , focused [ ' bodyBottom ' ] )
self . capture ( ' after-text-scale-200 ' )
self . code ( " await page.evaluate(()=>document.documentElement.style.fontSize= ' ' );await page.keyboard.press( ' Escape ' );return true; " )
if __name__ == ' __main__ ' :
unittest . main ( verbosity = 2 )