const { start, BASE: _BASE, DIR, assert, fs, path, sanitized, } = require('./portal-visual-qa-lib.cjs'); void (async () => { const q = await start('next-accessibility-chromium-20261010'); let page, _ctx; try { ({ page, ctx: _ctx } = await q.context('promoter')); await q.login(page, 'promoter'); q.proof.pages = []; const fixture = JSON.parse( fs.readFileSync( path.join(DIR, 'next-organization-chromium-20261010.json'), 'utf8', ), ).fixture; for (const route of [ '/investidor/profile', '/investidor/manifestacoes/' + fixture.new_submission, '/minhas-oportunidades/36/edit', '/minhas-oportunidades/51/resources', '/parcerias/salas/1', ]) { q.phase('Keyboard, accessible names and states ' + route); await q.open(page, route); await page.locator('main').first().waitFor(); await page.waitForLoadState('networkidle'); await page.addScriptTag({ path: path.join(DIR, 'qa-a11y-toolkit/package/axe.min.js'), }); const result = await page.evaluate(async () => { const r = await axe.run(document, { runOnly: { type: 'tag', values: [ 'wcag2a', 'wcag2aa', 'wcag21aa', 'best-practice', ], }, }); return { violations: r.violations.map((v) => ({ id: v.id, impact: v.impact, help: v.help, nodes: v.nodes.map((n) => ({ target: n.target, failureSummary: n.failureSummary, })), })), incomplete: r.incomplete.map((v) => ({ id: v.id, help: v.help, nodes: v.nodes.map((n) => n.target), })), passes: r.passes.length, }; }); const item = { route, ...result, keyboard: [] }; q.proof.pages.push(item); if (route.startsWith('/investidor/manifestacoes/')) { for (const name of [ 'País de residência', 'País de constituição da entidade', 'País de origem declarado desta solicitação', ]) assert.equal( await page .getByRole('combobox', { name, exact: true }) .count(), 1, ); assert.equal( await page .getByRole('combobox', { name: 'País de constituição da entidade', exact: true, }) .inputValue(), 'FR', ); assert( await page .getByText('Esta cópia conserva a origem', { exact: false, }) .isVisible(), ); item.countries_named_and_source_shown = true; } q.save(); await page.evaluate(() => { document.activeElement?.blur(); window.scrollTo(0, 0); }); let skip = false; for (let i = 0; i < 35; i++) { await page.keyboard.press('Tab'); const focused = await page.evaluate(() => { const e = document.activeElement, s = getComputedStyle(e); return { tag: e.tagName, id: e.id, role: e.getAttribute('role'), name: e.getAttribute('aria-label') || e.textContent?.trim().slice(0, 100) || e.getAttribute('name'), href: e.getAttribute('href'), outline: { style: s.outlineStyle, width: s.outlineWidth, color: s.outlineColor, }, visible: e.getBoundingClientRect().height > 0, }; }); item.keyboard.push(focused); if (focused.href === '#portal-content') { skip = true; await q.shot(page, 'skip-' + q.proof.pages.length, false); await page.keyboard.press('Enter'); assert( await page .locator('#portal-content') .evaluate((e) => document.activeElement === e), ); item.skip_reaches_content = true; break; } } assert(skip, 'Keyboard reaches skip link'); await page.keyboard.press('Tab'); item.after_skip = await page.evaluate(() => ({ tag: document.activeElement.tagName, id: document.activeElement.id, })); const menu = page.getByRole('button', { name: /Explorar investimentos/, }); if (await menu.isVisible()) { await menu.focus(); await page.keyboard.press('Enter'); assert.equal(await menu.getAttribute('aria-expanded'), 'true'); await page.keyboard.press('Escape'); assert.equal(await menu.getAttribute('aria-expanded'), 'false'); assert( await menu.evaluate((e) => document.activeElement === e), ); item.menu_keyboard_escape_returns_focus = true; } item.states = await page.getByRole('status').allTextContents(); item.alerts = await page.getByRole('alert').allTextContents(); if (route === '/parcerias/salas/1') { await page .getByRole('heading', { name: 'Documentos autorizados', exact: true, }) .scrollIntoViewIfNeeded(); item.document_state = await page .getByRole('status', { name: 'Documentos disponíveis nesta sala', exact: true, }) .textContent(); } if (route === '/investidor/profile') { const box = page.getByRole('region', { name: 'Cadastro mestre: Synthetic QA organization', exact: true, }), country = box.getByRole('combobox', { name: 'País de constituição da organização', exact: true, }); await country.selectOption(''); await box .getByLabel('Fonte ou referência deste país', { exact: true, }) .fill(''); const wait = page.waitForResponse( (r) => r.request().method() === 'PUT' && new URL(r.url()).pathname.endsWith('/country'), ); await box .getByRole('button', { name: 'Guardar país no cadastro mestre', exact: true, }) .click(); assert([302, 303].includes((await wait).status())); await page.waitForFunction( () => document.activeElement?.getAttribute('aria-invalid') === 'true', ); assert( await country.evaluate((e) => document.activeElement === e), ); item.validation = await country.evaluate((e) => ({ invalid: e.getAttribute('aria-invalid'), focused: document.activeElement === e, error: document.getElementById( e.getAttribute('aria-describedby'), )?.textContent, })); assert(item.validation.error?.includes('país de constituição')); assert.equal(await box.getByRole('alert').count(), 2); await q.shot(page, 'master-error-focus', false); } await q.shot(page, 'keyboard-' + q.proof.pages.length, false); q.save(); } q.proof.checks = { keyboard_and_skip_routes: q.proof.pages.every( (p) => p.skip_reaches_content, ), automated_violations: q.proof.pages.reduce( (n, p) => n + p.violations.length, 0, ), screen_reader_not_tested: true, real_devices_not_tested: true, }; q.proof.result = q.proof.checks.automated_violations ? 'violations_found' : 'passed'; } catch (e) { q.proof.result = 'failed'; q.proof.error = sanitized(e); if ( page && !['/login', '/two-factor-challenge'].includes( new URL(page.url()).pathname, ) ) await q.shot(page, 'failure', false).catch(() => {}); } finally { q.proof.completed_at = new Date().toISOString(); q.save(); await q.browser.close(); } console.log( JSON.stringify({ result: q.proof.result, checks: q.proof.checks, error: q.proof.error, pages: q.proof.pages?.map((p) => ({ route: p.route, violations: p.violations, })), }), ); if (q.proof.result === 'failed') process.exitCode = 1; })();