"""SO globales: recorrido HTTP real y Playwright MCP en instalación desechable.""" import json import base64 import hashlib import time import uuid def verify(context, fixture, admin_password): api, tool, capture = context['api'], context['tool'], context['capture'] origin, directory = context['origin'], context['directory'] checks, screenshots, measurements = [], [], [] base = '/catalogs/global-operating-systems' def code(client, source): print(' SO MCP: ' + source[:90], flush=True) try: return tool(client, 'browser_run_code_unsafe', {'code': 'async(page)=>{' + source + '}'}) except RuntimeError: shot(client, 'failure') diagnostic = context['result_json'](tool(client, 'browser_run_code_unsafe', {'code': "async(page)=>({alerts:await page.locator('[role=alert]').allTextContents(),dialogs:await page.getByRole('dialog').allTextContents(),buttons:await page.getByRole('button').evaluateAll(items=>items.filter(x=>x.getBoundingClientRect().width).map(x=>({text:x.innerText,disabled:x.disabled})))})"})) measurements.append({'failure': diagnostic}) raise def login(client, email, password): tool(client, 'browser_navigate', {'url': origin + '/login'}) code(client, "await page.getByRole('button',{name:'Entrar',exact:true}).waitFor();") tool(client, 'browser_fill_form', {'fields': [ {'name': 'Correo electrónico', 'type': 'textbox', 'target': 'input[type=email]', 'value': email}, {'name': 'Contraseña', 'type': 'textbox', 'target': 'input[type=password]', 'value': password}]}) tool(client, 'browser_click', {'target': 'button[type=submit]'}) code(client, "await page.locator('[data-slot=workspace]').waitFor({timeout:30000});") def shot(client, name): screenshots.append(capture(client, directory, 'global-os-' + name)) def report(): data = {'checks': checks, 'screenshots': screenshots, 'measurements': measurements, 'database': 'metis_desk_test', 'companies': 15, 'principal': {'people': 50, 'assetsAtLeast': 50, 'resourcesAtLeast': 50, 'profiles': 50, 'workstations': 50}, 'mcp': 'Docker stdio de .codex/config.toml', 'productionEnabled': False} (directory / 'global-operating-systems-report.json').write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n') with context['browser'](context['config']) as client: try: login(client, 'setup@audit.example.test', admin_password) tool(client, 'browser_resize', {'width': 1280, 'height': 1000}) tool(client, 'browser_navigate', {'url': origin + base}) code(client, "await page.getByText('Todavía no hay sistemas operativos globales.',{exact:false}).waitFor();") checks.append('empty-catalog') shot(client, 'empty') code(client, "await page.getByRole('button',{name:'Nuevo sistema operativo global',exact:true}).click(); await page.getByRole('dialog').getByRole('textbox',{name:'Nombre',exact:true}).fill('Windows 11 Pro'); await page.getByRole('textbox',{name:'Fabricante / proveedor',exact:true}).fill('Microsoft'); await page.getByRole('textbox',{name:'Familia',exact:true}).fill('Windows'); await page.getByRole('textbox',{name:'Versión principal del producto',exact:true}).fill('11'); await page.getByRole('combobox',{name:'Estado de edición',exact:true}).click(); await page.getByRole('option',{name:'Edición conocida',exact:true}).click(); await page.getByRole('textbox',{name:'Edición',exact:true}).fill('Pro');") code(client, "if(!await page.getByRole('button',{name:'Guardar',exact:true}).isDisabled())throw Error('Revisión obligatoria'); await page.getByRole('button',{name:'Comprobar identidad en el catálogo',exact:true}).click(); await page.getByText('No hay otra identidad equivalente en el catálogo.',{exact:false}).waitFor(); await page.getByRole('checkbox',{name:'He revisado la identidad y el catálogo; este producto es distinto.',exact:true}).check(); await page.getByRole('textbox',{name:'Edición',exact:true}).fill('Home'); if(!await page.getByRole('button',{name:'Guardar',exact:true}).isDisabled())throw Error('Revisión obsoleta'); await page.getByRole('textbox',{name:'Edición',exact:true}).fill('Pro');") checks.extend(['identity-fields', 'known-edition', 'review-required', 'identity-review-invalidation']) shot(client, 'create-review') code(client, "await page.getByRole('button',{name:'Guardar',exact:true}).click(); await page.getByRole('heading',{name:'Windows 11 Pro',exact:true}).waitFor(); await page.getByRole('heading',{name:'Historial',exact:true}).waitFor();") product_url = context['result_json'](code(client, 'return page.url();')) product_id = product_url.split('/')[-1] checks.extend(['create-in-browser', 'history']) shot(client, 'created-detail') code(client, "await page.getByRole('button',{name:'Editar sistema operativo global',exact:true}).click(); await page.getByRole('textbox',{name:'Versión principal del producto',exact:true}).fill('10'); await page.getByRole('textbox',{name:'Nombre',exact:true}).fill('Windows 10 Pro'); await page.getByRole('button',{name:'Comprobar identidad en el catálogo',exact:true}).click(); await page.getByRole('checkbox',{name:'He revisado la identidad y el catálogo; este producto es distinto.',exact:true}).check(); await page.getByRole('button',{name:'Guardar',exact:true}).click(); await page.getByText('Este producto ya se publicó.',{exact:false}).waitFor();") checks.append('server-rejects-product-transformation-form-preserved') shot(client, 'identity-error') code(client, "await page.getByRole('button',{name:'Cancelar',exact:true}).click(); await page.getByRole('dialog').waitFor({state:'hidden'}); if(!await page.getByRole('button',{name:'Editar sistema operativo global',exact:true}).evaluate(el=>el===document.activeElement))throw Error('Retorno de foco');") checks.append('dialog-focus-return') api.request('/auth/login', {'email': 'setup@audit.example.test', 'password': admin_password}) rc = api.request('/resource-catalog/global')['items'] ids = {item['builtinKey']: item['id'] for item in rc if item.get('builtinKey')} identity_keys = {'provider': 'provider', 'family': 'os_family', 'productVersion': 'os_product_version', 'editionState': 'os_edition_state', 'edition': 'software_edition'} for index in range(50): identity = {'provider': 'Microsoft', 'family': 'Windows', 'productVersion': str(100 + index), 'editionState': 'known', 'edition': 'Pro'} creation = str(uuid.UUID(int=(int(time.time()*1000)<<80) | (0x7<<76) | (0x2<<62) | (uuid.uuid4().int & ((1<<62)-1)))) api.request('/resource-global-operating-systems', {'creationId': creation, 'reviewedIdentity': identity, 'values': {'name': f'Windows {100+index} Pro — Producto sintético Álvarez', 'templateId': ids['template:os'], 'active': True, 'notes': '', 'characteristics': {ids['field:'+identity_keys[key]]: value for key, value in identity.items()}}}, expected=201) company = fixture['company'] local = api.request('/resources/' + company, {'values': {'name': 'Windows 11 Pro', 'templateId': ids['template:os'], 'active': True, 'notes': '', 'characteristics': {}}}, expected=201)['resource'] for resource_id in [product_id, local['id']]: api.request('/software-installations/' + company, {'values': {'assetId': fixture['softwareAssets'][0], 'resourceId': resource_id, 'licenseResourceId': None, 'version': '24H2', 'state': 'configured', 'notes': ''}}, expected=201) api.request('/auth/logout', {}) tool(client, 'browser_navigate', {'url': origin + base}) code(client, "await page.getByRole('link',{name:'Windows 11 Pro',exact:true}).waitFor(); await page.getByRole('button',{name:'Siguiente',exact:true}).click(); await page.getByRole('button',{name:'Anterior',exact:true}).click(); await page.getByRole('textbox',{name:'Buscar por nombre o proveedor',exact:true}).fill('SIN-COINCIDENCIAS-SO'); await page.getByText('No hay sistemas operativos que coincidan.',{exact:false}).waitFor();") checks.extend(['51-global-products', 'cursor-pagination', 'search-empty-recovery']) shot(client, 'no-results') code(client, "await page.getByRole('textbox',{name:'Buscar por nombre o proveedor',exact:true}).fill(''); await page.getByRole('link',{name:'Windows 11 Pro',exact:true}).waitFor();") for theme in ['light', 'dark']: code(client, "await page.evaluate(theme=>{document.documentElement.classList.toggle('dark',theme==='dark');}," + json.dumps(theme) + ");") for width in [320,390,768,1024,1280,1600]: tool(client, 'browser_resize', {'width': width, 'height': 1000 if width > 768 else 844}) code(client, "if(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1))throw Error('Desbordamiento de página');") shot(client, f'list-{theme}-{width}') contrast = context['result_json'](code(client, """return await page.evaluate(()=>{ const canvas=document.createElement('canvas');canvas.width=canvas.height=1;const ctx=canvas.getContext('2d',{willReadFrequently:true}); const rgba=color=>{ctx.clearRect(0,0,1,1);ctx.fillStyle=color;ctx.fillRect(0,0,1,1);return [...ctx.getImageData(0,0,1,1).data].map((v,i)=>i===3?v/255:v)}; const over=(a,b)=>{const alpha=a[3]+b[3]*(1-a[3]);return [...a.slice(0,3).map((v,i)=>(v*a[3]+b[i]*b[3]*(1-a[3]))/alpha),alpha]}; const bg=e=>e?over(rgba(getComputedStyle(e).backgroundColor),bg(e.parentElement)):[255,255,255,1]; const lum=c=>c.slice(0,3).map(v=>v/255).map(v=>v<=.04045?v/12.92:((v+.055)/1.055)**2.4).reduce((sum,v,i)=>sum+v*[.2126,.7152,.0722][i],0); return [...document.querySelectorAll('main h1,main p,main table a,main th')].filter(e=>e.getBoundingClientRect().width&&e.innerText.trim()).map(e=>{const a=lum(over(rgba(getComputedStyle(e).color),bg(e))),b=lum(bg(e));return {text:e.innerText.slice(0,80),ratio:(Math.max(a,b)+.05)/(Math.min(a,b)+.05)}}); });""")) if not contrast or any(item['ratio'] < 4.5 for item in contrast): raise RuntimeError('Contraste de texto insuficiente en ' + theme + ': ' + json.dumps(contrast)) measurements.append({'theme': theme, 'contrast': contrast}) checks.append('computed-text-contrast-' + theme) checks.append('list-' + theme + '-320-390-768-1024-1280-1600') tool(client, 'browser_resize', {'width': 390, 'height': 844}) code(client, "await page.getByRole('button',{name:'Nuevo sistema operativo global',exact:true}).click(); await page.getByRole('dialog').waitFor(); if(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1))throw Error('Desbordamiento de modal');") shot(client, 'mobile-form') code(client, "await page.keyboard.press('Escape'); await page.getByRole('dialog').waitFor({state:'hidden'});") checks.append('mobile-form-keyboard-close') tool(client, 'browser_resize', {'width': 1280, 'height': 1000}) code(client, "page.osPattern='**/api/resource-global-operating-systems?*'; await page.route(page.osPattern,r=>r.fulfill({status:500,contentType:'application/json',json:{code:'INTERNAL_ERROR'}})); await page.goto(" + json.dumps(origin+base) + "); await page.getByText('No se han podido cargar los sistemas operativos.',{exact:true}).waitFor();") shot(client, 'list-error') code(client, "await page.unroute(page.osPattern); await page.getByRole('button',{name:'Reintentar',exact:true}).click(); await page.getByRole('link',{name:'Windows 11 Pro',exact:true}).waitFor();") checks.append('transport-error-retry') # Una sola llamada conserva las barreras y sus callbacks en el mismo ámbito. loading_capture = context['result_json'](code(client, """ const releases=[], pending=[]; const pattern='**/api/resource-global-operating-systems?*'; const hold=async route=>{const task=(async()=>{await new Promise(resolve=>releases.push(resolve));await route.continue()})();pending.push(task);await task}; await page.route(pattern,hold); let screenshot; try { await page.goto(""" + json.dumps(origin+base) + """,{waitUntil:'domcontentloaded'}); await page.getByText('Cargando sistemas operativos…',{exact:true}).waitFor(); screenshot=(await page.screenshot({fullPage:false})).toString('base64'); } finally { for(const release of releases)release(); await Promise.all(pending); await page.unroute(pattern,hold); } await page.getByRole('link',{name:'Windows 11 Pro',exact:true}).waitFor(); return screenshot; """)) png = base64.b64decode(loading_capture, validate=True) (directory/'global-os-list-loading.png').write_bytes(png) screenshots.append({'file': 'global-os-list-loading.png', 'sha256': hashlib.sha256(png).hexdigest()}) checks.append('initial-loading') code(client, "await page.getByRole('button',{name:'Nuevo sistema operativo global',exact:true}).click(); await page.getByRole('textbox',{name:'Nombre',exact:true}).fill('Borrador de SO'); await page.getByRole('checkbox',{name:'Publicar como producto activo',exact:true}).uncheck(); await page.getByRole('button',{name:'Guardar',exact:true}).click(); await page.getByRole('heading',{name:'Borrador de SO',exact:true}).waitFor();") checks.append('inactive-draft-in-browser') code(client, "await page.getByRole('button',{name:'Editar sistema operativo global',exact:true}).click(); await page.keyboard.press('Escape'); await page.getByRole('dialog').waitFor({state:'hidden'}); await page.getByLabel('Menú de usuario',{exact:true}).click(); await page.getByRole('combobox',{name:'Idioma',exact:true}).click(); await page.getByRole('option',{name:'English',exact:true}).click(); await page.getByRole('combobox',{name:'Language',exact:true}).waitFor(); await page.getByLabel('User menu',{exact:true}).click(); await page.getByRole('button',{name:'Edit global operating system',exact:true}).click(); await page.getByRole('textbox',{name:'Major product version',exact:true}).waitFor(); await page.getByRole('combobox',{name:'Edition status',exact:true}).click(); await page.getByRole('option',{name:'Edition not applicable',exact:true}).waitFor(); await page.keyboard.press('Escape');") shot(client, 'english-form') checks.append('english-form-labels-edition-options') code(client, "await page.keyboard.press('Escape'); await page.getByRole('dialog').waitFor({state:'hidden'});") finally: report() with context['browser'](context['config']) as client: try: login(client, fixture['email'], fixture['password']) tool(client, 'browser_resize', {'width': 1280, 'height': 1000}) tool(client, 'browser_navigate', {'url': product_url}) code(client, "await page.getByRole('heading',{name:'Windows 11 Pro',exact:true}).waitFor(); await page.getByText('Versión principal del producto',{exact:true}).waitFor(); if(await page.getByRole('button',{name:'Editar sistema operativo global',exact:true}).count())throw Error('Administración ordinaria');") checks.append('ordinary-public-projection') shot(client, 'ordinary-detail') asset_url = origin + '/companies/' + company + '/assets/' + fixture['softwareAssets'][0] station_url = origin + '/companies/' + company + '/workstations/' + fixture['softwareWorkstations'][0] for view, url in [('asset',asset_url),('workstation',station_url)]: tool(client, 'browser_navigate', {'url': url}) code(client, "await page.locator('[data-current-operating-systems]').getByRole('link',{name:'Windows 11 Pro',exact:true}).first().waitFor(); if(!await page.locator('[data-current-operating-systems]').locator('a[href=\"" + base + '/' + product_id + "\"]').count())throw Error('Destino SO global');") code(client, "await page.locator('[data-current-operating-systems]').locator('xpath=ancestor::section[1]').scrollIntoViewIfNeeded();") shot(client, view + '-current-os') checks.append(view + '-global-os-navigation') global_href = base + '/' + product_id if view == 'asset': code(client, "const row=page.getByRole('row').filter({has:page.locator('a[href=\"" + global_href + "\"]')}); await row.getByRole('button',{name:'Acciones',exact:true}).click(); await page.getByRole('menuitem',{name:/Historial/}).click(); await page.getByRole('dialog',{name:/Historial/}).locator('a[href=\"" + global_href + "\"]').waitFor();") shot(client, 'installation-history-global-link') code(client, "await page.keyboard.press('Escape'); await page.getByRole('dialog').waitFor({state:'hidden'});") checks.append('installation-panel-and-history-global-os-navigation') else: code(client, "await page.locator('[data-workplace-software] a[href=\"" + global_href + "\"]').waitFor(); await page.locator('[data-workplace-map]').scrollIntoViewIfNeeded();") shot(client, 'workplace-map-global-os') code(client, "await page.locator('[data-workplace-software] a[href=\"" + global_href + "\"]').click(); await page.getByRole('heading',{name:'Windows 11 Pro',exact:true}).waitFor(); if(new URL(page.url()).pathname!==" + json.dumps(global_href) + ")throw Error('Destino del mapa');") checks.append('map-click-opens-global-os-detail') tool(client, 'browser_navigate', {'url': station_url}) code(client, "await page.locator('[data-workplace-equipment=principal] a').click(); await page.getByRole('heading',{name:'AUDIT-R23-PORT-01',exact:true}).waitFor();") checks.append('map-equipment-pointer-navigation') tool(client, 'browser_navigate', {'url': asset_url}) code(client, "await page.getByRole('button',{name:'Añadir instalación',exact:true}).click(); await page.getByRole('dialog',{name:'Añadir instalación',exact:true}).getByRole('button',{name:'Software',exact:true}).click(); await page.getByRole('dialog',{name:'Seleccionar software',exact:true}).getByRole('textbox',{name:'Buscar por nombre o proveedor',exact:true}).fill('Windows 11 Pro'); await page.getByRole('dialog',{name:'Seleccionar software',exact:true}).getByRole('cell',{name:'Windows 11 Pro',exact:true}).first().waitFor(); if(await page.getByRole('dialog',{name:'Seleccionar software',exact:true}).getByRole('cell',{name:'Windows 11 Pro',exact:true}).count()!==2)throw Error('Coexistencia');") shot(client, 'mixed-picker') checks.append('mixed-picker-global-local-no-replacement') code(client, "await page.keyboard.press('Escape'); await page.keyboard.press('Escape');") finally: report() print('SO global: recorrido MCP verificado. Evidencias: ' + str(directory.relative_to(context['ROOT'])), flush=True)