@brandlab-digital/conformance 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +26 -0
  2. package/dist/current-release.js +5 -5
  3. package/dist/current-release.js.map +1 -1
  4. package/dist/public-copy.d.ts +7 -0
  5. package/dist/public-copy.d.ts.map +1 -1
  6. package/dist/public-copy.js +8 -3
  7. package/dist/public-copy.js.map +1 -1
  8. package/dist/ready-cli.d.ts +38 -0
  9. package/dist/ready-cli.d.ts.map +1 -0
  10. package/dist/ready-cli.js +327 -0
  11. package/dist/ready-cli.js.map +1 -0
  12. package/dist/site-system/checks.d.ts +389 -0
  13. package/dist/site-system/checks.d.ts.map +1 -0
  14. package/dist/site-system/checks.js +96 -0
  15. package/dist/site-system/checks.js.map +1 -0
  16. package/dist/site-system/config.d.ts +40 -0
  17. package/dist/site-system/config.d.ts.map +1 -0
  18. package/dist/site-system/config.js +214 -0
  19. package/dist/site-system/config.js.map +1 -0
  20. package/dist/site-system/index.d.ts +14 -0
  21. package/dist/site-system/index.d.ts.map +1 -0
  22. package/dist/site-system/index.js +14 -0
  23. package/dist/site-system/index.js.map +1 -0
  24. package/dist/site-system/journeys/access.d.ts +7 -0
  25. package/dist/site-system/journeys/access.d.ts.map +1 -0
  26. package/dist/site-system/journeys/access.js +263 -0
  27. package/dist/site-system/journeys/access.js.map +1 -0
  28. package/dist/site-system/journeys/browser.d.ts +79 -0
  29. package/dist/site-system/journeys/browser.d.ts.map +1 -0
  30. package/dist/site-system/journeys/browser.js +218 -0
  31. package/dist/site-system/journeys/browser.js.map +1 -0
  32. package/dist/site-system/journeys/conversion.d.ts +3 -0
  33. package/dist/site-system/journeys/conversion.d.ts.map +1 -0
  34. package/dist/site-system/journeys/conversion.js +152 -0
  35. package/dist/site-system/journeys/conversion.js.map +1 -0
  36. package/dist/site-system/journeys/menu.d.ts +11 -0
  37. package/dist/site-system/journeys/menu.d.ts.map +1 -0
  38. package/dist/site-system/journeys/menu.js +395 -0
  39. package/dist/site-system/journeys/menu.js.map +1 -0
  40. package/dist/site-system/journeys/page-helpers.d.ts +146 -0
  41. package/dist/site-system/journeys/page-helpers.d.ts.map +1 -0
  42. package/dist/site-system/journeys/page-helpers.js +426 -0
  43. package/dist/site-system/journeys/page-helpers.js.map +1 -0
  44. package/dist/site-system/journeys/plan.d.ts +82 -0
  45. package/dist/site-system/journeys/plan.d.ts.map +1 -0
  46. package/dist/site-system/journeys/plan.js +112 -0
  47. package/dist/site-system/journeys/plan.js.map +1 -0
  48. package/dist/site-system/journeys/shell.d.ts +24 -0
  49. package/dist/site-system/journeys/shell.d.ts.map +1 -0
  50. package/dist/site-system/journeys/shell.js +269 -0
  51. package/dist/site-system/journeys/shell.js.map +1 -0
  52. package/dist/site-system/playwright-report.d.ts +22 -0
  53. package/dist/site-system/playwright-report.d.ts.map +1 -0
  54. package/dist/site-system/playwright-report.js +131 -0
  55. package/dist/site-system/playwright-report.js.map +1 -0
  56. package/dist/site-system/records.d.ts +30 -0
  57. package/dist/site-system/records.d.ts.map +1 -0
  58. package/dist/site-system/records.js +98 -0
  59. package/dist/site-system/records.js.map +1 -0
  60. package/dist/site-system/report.d.ts +116 -0
  61. package/dist/site-system/report.d.ts.map +1 -0
  62. package/dist/site-system/report.js +366 -0
  63. package/dist/site-system/report.js.map +1 -0
  64. package/dist/site-system/rule-modules.d.ts +8 -0
  65. package/dist/site-system/rule-modules.d.ts.map +1 -0
  66. package/dist/site-system/rule-modules.js +12 -0
  67. package/dist/site-system/rule-modules.js.map +1 -0
  68. package/dist/site-system/rules/design/d4-distinctness.d.ts +36 -0
  69. package/dist/site-system/rules/design/d4-distinctness.d.ts.map +1 -0
  70. package/dist/site-system/rules/design/d4-distinctness.js +207 -0
  71. package/dist/site-system/rules/design/d4-distinctness.js.map +1 -0
  72. package/dist/site-system/rules/design/index.d.ts +17 -0
  73. package/dist/site-system/rules/design/index.d.ts.map +1 -0
  74. package/dist/site-system/rules/design/index.js +7 -0
  75. package/dist/site-system/rules/design/index.js.map +1 -0
  76. package/dist/site-system/rules/enquiry/c2-quote-form.d.ts +31 -0
  77. package/dist/site-system/rules/enquiry/c2-quote-form.d.ts.map +1 -0
  78. package/dist/site-system/rules/enquiry/c2-quote-form.js +211 -0
  79. package/dist/site-system/rules/enquiry/c2-quote-form.js.map +1 -0
  80. package/dist/site-system/rules/enquiry/common.d.ts +37 -0
  81. package/dist/site-system/rules/enquiry/common.d.ts.map +1 -0
  82. package/dist/site-system/rules/enquiry/common.js +59 -0
  83. package/dist/site-system/rules/enquiry/common.js.map +1 -0
  84. package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts +8 -0
  85. package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts.map +1 -0
  86. package/dist/site-system/rules/enquiry/e1-recorded-titles.js +59 -0
  87. package/dist/site-system/rules/enquiry/e1-recorded-titles.js.map +1 -0
  88. package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts +22 -0
  89. package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts.map +1 -0
  90. package/dist/site-system/rules/enquiry/e9-cost-guides.js +55 -0
  91. package/dist/site-system/rules/enquiry/e9-cost-guides.js.map +1 -0
  92. package/dist/site-system/rules/enquiry/html.d.ts +53 -0
  93. package/dist/site-system/rules/enquiry/html.d.ts.map +1 -0
  94. package/dist/site-system/rules/enquiry/html.js +294 -0
  95. package/dist/site-system/rules/enquiry/html.js.map +1 -0
  96. package/dist/site-system/rules/enquiry/index.d.ts +17 -0
  97. package/dist/site-system/rules/enquiry/index.d.ts.map +1 -0
  98. package/dist/site-system/rules/enquiry/index.js +26 -0
  99. package/dist/site-system/rules/enquiry/index.js.map +1 -0
  100. package/dist/site-system/rules/enquiry/k8-sections.d.ts +17 -0
  101. package/dist/site-system/rules/enquiry/k8-sections.d.ts.map +1 -0
  102. package/dist/site-system/rules/enquiry/k8-sections.js +76 -0
  103. package/dist/site-system/rules/enquiry/k8-sections.js.map +1 -0
  104. package/dist/site-system/rules/enquiry/literal.d.ts +29 -0
  105. package/dist/site-system/rules/enquiry/literal.d.ts.map +1 -0
  106. package/dist/site-system/rules/enquiry/literal.js +189 -0
  107. package/dist/site-system/rules/enquiry/literal.js.map +1 -0
  108. package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts +73 -0
  109. package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts.map +1 -0
  110. package/dist/site-system/rules/enquiry/t11-licence-classes.js +275 -0
  111. package/dist/site-system/rules/enquiry/t11-licence-classes.js.map +1 -0
  112. package/dist/site-system/rules/enquiry/t5-reviews.d.ts +23 -0
  113. package/dist/site-system/rules/enquiry/t5-reviews.d.ts.map +1 -0
  114. package/dist/site-system/rules/enquiry/t5-reviews.js +91 -0
  115. package/dist/site-system/rules/enquiry/t5-reviews.js.map +1 -0
  116. package/dist/site-system/rules/shell/banned-wording.d.ts +24 -0
  117. package/dist/site-system/rules/shell/banned-wording.d.ts.map +1 -0
  118. package/dist/site-system/rules/shell/banned-wording.js +212 -0
  119. package/dist/site-system/rules/shell/banned-wording.js.map +1 -0
  120. package/dist/site-system/rules/shell/budgets.d.ts +43 -0
  121. package/dist/site-system/rules/shell/budgets.d.ts.map +1 -0
  122. package/dist/site-system/rules/shell/budgets.js +297 -0
  123. package/dist/site-system/rules/shell/budgets.js.map +1 -0
  124. package/dist/site-system/rules/shell/cta-labels.d.ts +17 -0
  125. package/dist/site-system/rules/shell/cta-labels.d.ts.map +1 -0
  126. package/dist/site-system/rules/shell/cta-labels.js +86 -0
  127. package/dist/site-system/rules/shell/cta-labels.js.map +1 -0
  128. package/dist/site-system/rules/shell/facts.d.ts +33 -0
  129. package/dist/site-system/rules/shell/facts.d.ts.map +1 -0
  130. package/dist/site-system/rules/shell/facts.js +217 -0
  131. package/dist/site-system/rules/shell/facts.js.map +1 -0
  132. package/dist/site-system/rules/shell/headings.d.ts +5 -0
  133. package/dist/site-system/rules/shell/headings.d.ts.map +1 -0
  134. package/dist/site-system/rules/shell/headings.js +23 -0
  135. package/dist/site-system/rules/shell/headings.js.map +1 -0
  136. package/dist/site-system/rules/shell/hero-location.d.ts +4 -0
  137. package/dist/site-system/rules/shell/hero-location.d.ts.map +1 -0
  138. package/dist/site-system/rules/shell/hero-location.js +45 -0
  139. package/dist/site-system/rules/shell/hero-location.js.map +1 -0
  140. package/dist/site-system/rules/shell/html.d.ts +53 -0
  141. package/dist/site-system/rules/shell/html.d.ts.map +1 -0
  142. package/dist/site-system/rules/shell/html.js +341 -0
  143. package/dist/site-system/rules/shell/html.js.map +1 -0
  144. package/dist/site-system/rules/shell/index.d.ts +18 -0
  145. package/dist/site-system/rules/shell/index.d.ts.map +1 -0
  146. package/dist/site-system/rules/shell/index.js +65 -0
  147. package/dist/site-system/rules/shell/index.js.map +1 -0
  148. package/dist/site-system/rules/shell/licence.d.ts +4 -0
  149. package/dist/site-system/rules/shell/licence.d.ts.map +1 -0
  150. package/dist/site-system/rules/shell/licence.js +60 -0
  151. package/dist/site-system/rules/shell/licence.js.map +1 -0
  152. package/dist/site-system/rules/shell/location-wording.d.ts +13 -0
  153. package/dist/site-system/rules/shell/location-wording.d.ts.map +1 -0
  154. package/dist/site-system/rules/shell/location-wording.js +15 -0
  155. package/dist/site-system/rules/shell/location-wording.js.map +1 -0
  156. package/dist/site-system/rules/shell/navigation.d.ts +5 -0
  157. package/dist/site-system/rules/shell/navigation.d.ts.map +1 -0
  158. package/dist/site-system/rules/shell/navigation.js +272 -0
  159. package/dist/site-system/rules/shell/navigation.js.map +1 -0
  160. package/dist/site-system/rules/shell/overlays.d.ts +4 -0
  161. package/dist/site-system/rules/shell/overlays.d.ts.map +1 -0
  162. package/dist/site-system/rules/shell/overlays.js +93 -0
  163. package/dist/site-system/rules/shell/overlays.js.map +1 -0
  164. package/dist/site-system/rules/shell/page.d.ts +51 -0
  165. package/dist/site-system/rules/shell/page.d.ts.map +1 -0
  166. package/dist/site-system/rules/shell/page.js +256 -0
  167. package/dist/site-system/rules/shell/page.js.map +1 -0
  168. package/dist/site-system/rules/shell/phone.d.ts +13 -0
  169. package/dist/site-system/rules/shell/phone.d.ts.map +1 -0
  170. package/dist/site-system/rules/shell/phone.js +99 -0
  171. package/dist/site-system/rules/shell/phone.js.map +1 -0
  172. package/dist/site-system/rules/shell/proof-numbers.d.ts +7 -0
  173. package/dist/site-system/rules/shell/proof-numbers.d.ts.map +1 -0
  174. package/dist/site-system/rules/shell/proof-numbers.js +81 -0
  175. package/dist/site-system/rules/shell/proof-numbers.js.map +1 -0
  176. package/dist/site-system/rules/shell/types.d.ts +80 -0
  177. package/dist/site-system/rules/shell/types.d.ts.map +1 -0
  178. package/dist/site-system/rules/shell/types.js +17 -0
  179. package/dist/site-system/rules/shell/types.js.map +1 -0
  180. package/dist/site-system/runners.d.ts +37 -0
  181. package/dist/site-system/runners.d.ts.map +1 -0
  182. package/dist/site-system/runners.js +48 -0
  183. package/dist/site-system/runners.js.map +1 -0
  184. package/dist/site-system/samples.d.ts +43 -0
  185. package/dist/site-system/samples.d.ts.map +1 -0
  186. package/dist/site-system/samples.js +99 -0
  187. package/dist/site-system/samples.js.map +1 -0
  188. package/dist/site-system/steps.d.ts +66 -0
  189. package/dist/site-system/steps.d.ts.map +1 -0
  190. package/dist/site-system/steps.js +45 -0
  191. package/dist/site-system/steps.js.map +1 -0
  192. package/dist/site-system/streamed-html.d.ts +9 -0
  193. package/dist/site-system/streamed-html.d.ts.map +1 -0
  194. package/dist/site-system/streamed-html.js +43 -0
  195. package/dist/site-system/streamed-html.js.map +1 -0
  196. package/dist/site-system-e2e.d.ts +6 -0
  197. package/dist/site-system-e2e.d.ts.map +1 -0
  198. package/dist/site-system-e2e.js +54 -0
  199. package/dist/site-system-e2e.js.map +1 -0
  200. package/package.json +22 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../../src/site-system/journeys/access.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K3 to K6 in the browser. K3: text contrast (axe `color-contrast`: 4.5:1, 3:1 for large text) and the non-text\n * contrast of icon-only controls (3:1) in the header, menu panels, drawer and footer. K4: primary actions (call, quote,\n * Menu) at least 44 px on phones; every other target 24 px or spaced (axe `target-size`). K5: Tab through every\n * focusable element at 390 px; none is ever fully hidden under the sticky header or the call bar (in every browser\n * project the site runs). K6: the phone and quote actions in one order in the header, the hero and the call bar.\n */\nimport { expect, test, type Locator, type Page } from '@playwright/test';\nimport {\n describeAxe,\n mainNavigation,\n menuDisclosures,\n note,\n openSitePage,\n panelLocator,\n panelOpen,\n raiseTimeout,\n runAxe,\n siteFooter,\n siteHeader,\n waitUntil,\n} from './browser.js';\nimport { SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT, siteSystemJourneyTitle as title, type SiteSystemJourneyPlan } from './plan.js';\nimport { callBarLinks, menuButton, openDrawer } from './shell.js';\n\n/** The shortest side a primary action (call, quote, Menu) may have on a phone, with half a pixel for rounding. */\nexport const PRIMARY_TARGET_PX = 44;\n\nconst SCOPE = Object.freeze({\n header: '[data-brandlab-journey~=\"header\"]',\n nav: '[data-brandlab-journey~=\"nav\"]',\n panel: '[data-brandlab-journey~=\"panel\"]',\n drawer: '[data-brandlab-journey~=\"drawer\"]',\n footer: '[data-brandlab-journey~=\"footer\"]',\n callBar: '[data-brandlab-journey~=\"call-bar\"]',\n});\n\nasync function iconProblems(region: Locator, label: string): Promise<string[]> {\n if (!(await region.count())) return [];\n const result = await region.evaluate((element) => window.__brandlabSiteSystem.iconContrastProblems(element));\n return result.problems.map((problem) => `${label}: ${problem}`);\n}\n\n/** Open each desktop panel in turn and run `check` while it is open. */\nasync function withEachPanel(page: Page, plan: SiteSystemJourneyPlan, check: (label: string) => Promise<void>): Promise<void> {\n const nav = await mainNavigation(page, plan);\n if (!nav) return;\n for (const disclosure of await menuDisclosures(nav)) {\n await disclosure.trigger.click();\n if (!(await waitUntil(() => panelOpen(page, disclosure), 500))) continue;\n await page.waitForTimeout(250);\n await panelLocator(page, disclosure.panelId).evaluate((panel) => window.__brandlabSiteSystem.markElement(panel, 'panel'));\n await check(disclosure.label);\n await disclosure.trigger.click();\n await page.waitForTimeout(100);\n }\n}\n\nexport function defineAccessJourneys(plan: SiteSystemJourneyPlan, runs: ReadonlySet<string>, options: { chromiumOnly: () => void }): void {\n const quote = { source: plan.quoteName.source, flags: plan.quoteName.flags };\n\n test.describe('contrast, targets and order', () => {\n options.chromiumOnly();\n\n if (runs.has('K3')) {\n test(title('K3', 'text and icons in the header, menu panels and footer meet contrast at 1440 px'), async ({ page }, testInfo) => {\n await page.setViewportSize({ ...SITE_SYSTEM_DESKTOP_VIEWPORT });\n await openSitePage(page, plan.home, plan);\n const header = await siteHeader(page);\n const footer = await siteFooter(page);\n await mainNavigation(page, plan);\n const problems: string[] = [];\n const text = await runAxe(page, [SCOPE.header, SCOPE.nav, SCOPE.footer], { rules: ['color-contrast'] });\n let unmeasured = text.incomplete;\n problems.push(...describeAxe(text.violations));\n problems.push(...(await iconProblems(header, 'header')), ...(await iconProblems(footer, 'footer')));\n await withEachPanel(page, plan, async (label) => {\n const open = await runAxe(page, [SCOPE.panel], { rules: ['color-contrast'] });\n unmeasured += open.incomplete;\n problems.push(...describeAxe(open.violations).map((line) => `${label} open: ${line}`));\n problems.push(...(await iconProblems(page.locator(SCOPE.panel).filter({ visible: true }).first(), `${label} panel`)));\n });\n note(testInfo, `axe could not measure ${unmeasured} colour result(s) (text over images or overlapping layers); review them by eye`);\n expect([...new Set(problems)]).toEqual([]);\n });\n\n test(title('K3', 'text and icons in the phone header, drawer and footer meet contrast at 390 px'), async ({ page }, testInfo) => {\n await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });\n await openSitePage(page, plan.home, plan);\n const header = await siteHeader(page);\n const footer = await siteFooter(page);\n const problems: string[] = [];\n const text = await runAxe(page, [SCOPE.header, SCOPE.footer], { rules: ['color-contrast'] });\n let unmeasured = text.incomplete;\n problems.push(...describeAxe(text.violations));\n problems.push(...(await iconProblems(header, 'header')), ...(await iconProblems(footer, 'footer')));\n const button = menuButton(header, plan);\n if (await button.count()) {\n const drawer = await openDrawer(page, button, 'click');\n if (drawer) {\n const group = drawer.locator('button[aria-expanded=\"false\"], summary').filter({ visible: true }).first();\n if (await group.count()) await group.click();\n await page.waitForTimeout(150);\n const inDrawer = await runAxe(page, [SCOPE.drawer], { rules: ['color-contrast'] });\n unmeasured += inDrawer.incomplete;\n problems.push(...describeAxe(inDrawer.violations).map((line) => `drawer: ${line}`));\n problems.push(...(await iconProblems(drawer, 'drawer')));\n }\n }\n note(testInfo, `axe could not measure ${unmeasured} colour result(s) (text over images or overlapping layers); review them by eye`);\n expect([...new Set(problems)]).toEqual([]);\n });\n }\n\n if (runs.has('K4')) {\n test(title('K4', 'the call, quote and Menu actions are at least 44 px on phones'), async ({ page }, testInfo) => {\n raiseTimeout(testInfo, 180_000);\n await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });\n const problems: string[] = [];\n const measure = async (label: string, finder: (name: { source: string; flags: string }) => { description: string; width: number; height: number }[]) => {\n const targets = await page.evaluate(finder, quote);\n for (const target of targets) {\n if (target.width < PRIMARY_TARGET_PX - 0.5 || target.height < PRIMARY_TARGET_PX - 0.5) {\n problems.push(`${label}: ${target.description} is ${Math.round(target.width)} x ${Math.round(target.height)} px (primary actions need 44 x 44)`);\n }\n }\n return targets.length;\n };\n for (const path of [plan.home, ...plan.money]) {\n await openSitePage(page, path, plan);\n const header = await siteHeader(page);\n await measure(`${path} header`, (name) => {\n const helpers = window.__brandlabSiteSystem;\n const root = document.querySelector('[data-brandlab-journey~=\"header\"]');\n if (!root) return [];\n const targets = [...Array.from(root.querySelectorAll('a[href^=\"tel:\"]')).filter(helpers.isRendered), ...helpers.quoteActions(root, name)];\n return targets.map((element) => ({ description: helpers.describeElement(element), ...element.getBoundingClientRect().toJSON() as { width: number; height: number } }));\n });\n const menu = menuButton(header, plan);\n if (await menu.count()) {\n const box = await menu.boundingBox();\n if (box && (box.width < PRIMARY_TARGET_PX - 0.5 || box.height < PRIMARY_TARGET_PX - 0.5)) problems.push(`${path} header: the Menu button is ${Math.round(box.width)} x ${Math.round(box.height)} px (primary actions need 44 x 44)`);\n }\n await measure(`${path} hero`, (name) => {\n const helpers = window.__brandlabSiteSystem;\n const hero = helpers.heroRegion(name);\n if (!hero) return [];\n const targets = [...Array.from(hero.querySelectorAll('a[href^=\"tel:\"]')).filter(helpers.isRendered), ...helpers.quoteActions(hero, name)];\n return targets.map((element) => ({ description: helpers.describeElement(element), ...element.getBoundingClientRect().toJSON() as { width: number; height: number } }));\n });\n if (path === plan.home && (await menu.count())) {\n if (await openDrawer(page, menu, 'click')) {\n await measure('drawer', (name) => {\n const helpers = window.__brandlabSiteSystem;\n const drawer = document.querySelector('[data-brandlab-journey~=\"drawer\"]');\n if (!drawer) return [];\n const targets = [...Array.from(drawer.querySelectorAll('a[href^=\"tel:\"]')).filter(helpers.isRendered), ...helpers.quoteActions(drawer, name)];\n return targets.map((element) => ({ description: helpers.describeElement(element), ...element.getBoundingClientRect().toJSON() as { width: number; height: number } }));\n });\n await page.keyboard.press('Escape');\n await page.waitForTimeout(200);\n }\n }\n if (await callBarLinks(page)) {\n await measure(`${path} call bar`, (name) => {\n const helpers = window.__brandlabSiteSystem;\n const bar = document.querySelector('[data-brandlab-journey~=\"call-bar\"]');\n if (!bar) return [];\n const targets = [...Array.from(bar.querySelectorAll('a[href^=\"tel:\"]')).filter(helpers.isRendered), ...helpers.quoteActions(bar, name)];\n return targets.map((element) => ({ description: helpers.describeElement(element), ...element.getBoundingClientRect().toJSON() as { width: number; height: number } }));\n });\n }\n }\n expect([...new Set(problems)]).toEqual([]);\n });\n\n test(title('K4', 'every other target in the header, panels, drawer and footer is at least 24 px or spaced apart'), async ({ page }) => {\n const problems: string[] = [];\n await page.setViewportSize({ ...SITE_SYSTEM_DESKTOP_VIEWPORT });\n await openSitePage(page, plan.home, plan);\n await siteHeader(page);\n await siteFooter(page);\n await mainNavigation(page, plan);\n problems.push(...describeAxe((await runAxe(page, [SCOPE.header, SCOPE.nav, SCOPE.footer], { rules: ['target-size'] })).violations).map((line) => `1440 px: ${line}`));\n await withEachPanel(page, plan, async (label) => {\n problems.push(...describeAxe((await runAxe(page, [SCOPE.panel], { rules: ['target-size'] })).violations).map((line) => `1440 px, ${label} open: ${line}`));\n });\n await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });\n await openSitePage(page, plan.home, plan);\n const header = await siteHeader(page);\n await siteFooter(page);\n problems.push(...describeAxe((await runAxe(page, [SCOPE.header, SCOPE.footer], { rules: ['target-size'] })).violations).map((line) => `390 px: ${line}`));\n const menu = menuButton(header, plan);\n if ((await menu.count()) && (await openDrawer(page, menu, 'click'))) {\n problems.push(...describeAxe((await runAxe(page, [SCOPE.drawer], { rules: ['target-size'] })).violations).map((line) => `390 px drawer: ${line}`));\n }\n expect([...new Set(problems)]).toEqual([]);\n });\n }\n\n if (runs.has('K6')) {\n for (const path of plan.sampled) {\n test(title('K6', `the phone and quote actions keep the ${plan.actionOrder} order in the header, hero and call bar on ${path}`), async ({ page }, testInfo) => {\n raiseTimeout(testInfo, 180_000);\n const problems: string[] = [];\n const seen: string[] = [];\n const record = (where: string, order: string) => {\n if (order === 'incomplete') return;\n seen.push(`${where}: ${order}`);\n if (order !== plan.actionOrder) problems.push(`${where} shows the ${order === 'phone-first' ? 'phone before the quote' : 'quote before the phone'} (the site's order is ${plan.actionOrder})`);\n };\n for (const viewport of [SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT]) {\n await page.setViewportSize({ ...viewport });\n await openSitePage(page, path, plan);\n const header = await siteHeader(page);\n if (await header.count()) record(`${viewport.width} px header`, await header.evaluate((element, name) => window.__brandlabSiteSystem.actionOrder(element, name), quote));\n record(`${viewport.width} px hero`, await page.evaluate((name) => {\n const hero = window.__brandlabSiteSystem.heroRegion(name);\n return hero ? window.__brandlabSiteSystem.actionOrder(hero, name) : 'incomplete';\n }, quote));\n if (viewport.width < 1024 && (await callBarLinks(page))) {\n record(`${viewport.width} px call bar`, await page.locator(SCOPE.callBar).first().evaluate((element, name) => window.__brandlabSiteSystem.actionOrder(element, name), quote));\n }\n }\n note(testInfo, seen.length ? `Orders seen: ${seen.join('; ')}` : 'No region shows both the phone and the quote action.');\n if (path === plan.home && !seen.some((entry) => entry.startsWith(`${SITE_SYSTEM_DESKTOP_VIEWPORT.width} px`))) {\n problems.push(`at ${SITE_SYSTEM_DESKTOP_VIEWPORT.width} px no region of the home page shows both the phone and the quote action, so their order cannot be checked`);\n }\n expect(problems).toEqual([]);\n });\n }\n }\n });\n\n if (runs.has('K5')) {\n test.describe('focus at 390 px', () => {\n for (const path of plan.sampled) {\n test(title('K5', `focus is never hidden under the sticky header or call bar on ${path} at 390 px`), async ({ page, browserName }, testInfo) => {\n raiseTimeout(testInfo, 240_000);\n await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });\n await openSitePage(page, path, plan);\n await siteHeader(page);\n const reachable = await page.evaluate(() => window.__brandlabSiteSystem.tabbableCount());\n const limit = Math.min(plan.maxTabStops, reachable + 10);\n // WebKit on macOS moves focus to links only with Option held.\n const key = browserName === 'webkit' && process.platform === 'darwin' ? 'Alt+Tab' : 'Tab';\n const hidden: string[] = [];\n let stops = 0;\n let outside = 0;\n for (let press = 0; press < limit; press += 1) {\n await page.keyboard.press(key);\n // Measure one frame later: a bar that hides on its observer's next frame is not caught mid-transition.\n const step = await page.evaluate(() => window.__brandlabSiteSystem.focusStepAfterFrame());\n if (step.repeat) break;\n if (step.done) {\n outside += 1;\n if (outside >= 2) break;\n continue;\n }\n stops += 1;\n if (step.obscured) hidden.push(`${step.obscured} is hidden under the ${step.covered}`);\n }\n if (stops >= plan.maxTabStops) note(testInfo, `${path}: stopped after ${plan.maxTabStops} Tab presses (about ${reachable} focusable elements); raise maxTabStops to walk the rest`);\n note(testInfo, `${path}: ${stops} focus stop(s) checked in ${browserName}`);\n expect(stops, `${path}: Tab reached no element`).toBeGreaterThan(0);\n expect(hidden).toEqual([]);\n });\n }\n });\n }\n}\n"]}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Browser plumbing for the site-system journeys: opening a page, finding the header parts by role (with the shared
3
+ * `data-bl` hooks only as a fast path), axe and the in-page helpers.
4
+ */
5
+ import { type Locator, type Page, type TestInfo } from '@playwright/test';
6
+ import type { SiteSystemJourneyPlan } from './plan.js';
7
+ import { type SiteSystemPageHelpers } from './page-helpers.js';
8
+ declare global {
9
+ interface Window {
10
+ __brandlabSiteSystem: SiteSystemPageHelpers;
11
+ }
12
+ }
13
+ /** Define `window.__brandlabSiteSystem` in every document this page loads. */
14
+ export declare function installPageHelpers(page: Page): Promise<void>;
15
+ /** Record a note on the running test; brandlab-ready adds it to the check's finding message. */
16
+ export declare function note(testInfo: TestInfo, description: string): void;
17
+ export declare function raiseTimeout(testInfo: TestInfo, budget: number): void;
18
+ /**
19
+ * Open a sampled page and wait for it to settle: the document (DOMContentLoaded, not load: `next start`'s image
20
+ * optimiser can stall first image requests), then load and the network (capped), then hydration where the shared
21
+ * header reports it, then two frames. A navigation timeout is retried once.
22
+ */
23
+ export declare function openSitePage(page: Page, path: string, plan: Pick<SiteSystemJourneyPlan, 'navigationTimeoutMs' | 'settleTimeoutMs'>): Promise<void>;
24
+ /** Wait for load and the network (capped), the shared header's hydration flag when it has one, and two frames. */
25
+ export declare function settle(page: Page, settleTimeoutMs: number): Promise<void>;
26
+ /** The site header: the shared header, else the page's banner landmark. Marked for axe as `header`. */
27
+ export declare function siteHeader(page: Page): Promise<Locator>;
28
+ /** The site footer: the shared footer, else the page's contentinfo landmark. Marked for axe as `footer`. */
29
+ export declare function siteFooter(page: Page): Promise<Locator>;
30
+ /** The visible main menu, by its accessible name. Marked for axe as `nav`. */
31
+ export declare function mainNavigation(page: Page, plan: Pick<SiteSystemJourneyPlan, 'navName'>): Promise<Locator | null>;
32
+ export interface Disclosure {
33
+ /** The disclosure button (`button[aria-expanded][aria-controls]`). */
34
+ trigger: Locator;
35
+ /** The id its aria-controls names. */
36
+ panelId: string;
37
+ label: string;
38
+ }
39
+ /** The menu's visible disclosure buttons, each with the panel it controls. */
40
+ export declare function menuDisclosures(nav: Locator): Promise<Disclosure[]>;
41
+ /** Menu links hidden at rest: a sign of submenus that a disclosure button should control. */
42
+ export declare function hiddenMenuLinks(nav: Locator): Promise<number>;
43
+ export declare function panelLocator(page: Page, panelId: string): Locator;
44
+ export declare function panelOpen(page: Page, disclosure: Disclosure): Promise<boolean>;
45
+ /** Every disclosure's panel is hidden and every button says so. */
46
+ export declare function openPanels(page: Page, disclosures: readonly Disclosure[]): Promise<string[]>;
47
+ /** Where to point to hover a disclosure: its item's top-level link, else the button itself. */
48
+ export declare function hoverPoint(disclosure: Disclosure): Promise<{
49
+ x: number;
50
+ y: number;
51
+ }>;
52
+ /** A neutral spot for the pointer: not on a control, the header or the menu. */
53
+ export declare function restingPoint(page: Page): Promise<{
54
+ x: number;
55
+ y: number;
56
+ }>;
57
+ /** Wait until `condition` holds or `timeoutMs` passes; returns whether it held. */
58
+ export declare function waitUntil(condition: () => Promise<boolean>, timeoutMs: number, intervalMs?: number): Promise<boolean>;
59
+ export interface AxeFinding {
60
+ id: string;
61
+ impact: string | null;
62
+ help: string;
63
+ targets: string[];
64
+ }
65
+ export interface AxeOutcome {
66
+ violations: AxeFinding[];
67
+ incomplete: number;
68
+ }
69
+ /** WCAG 2.2 A and AA, as the prototype's check ran it. */
70
+ export declare const SITE_SYSTEM_AXE_TAGS: readonly string[];
71
+ /** Run axe over the parts named by `include` selectors, with WCAG tags or named rules. */
72
+ export declare function runAxe(page: Page, include: readonly string[], only: {
73
+ tags?: readonly string[];
74
+ rules?: readonly string[];
75
+ }): Promise<AxeOutcome>;
76
+ export declare function describeAxe(findings: readonly AxeFinding[]): string[];
77
+ /** Serious and critical findings: the bar the prototype set for N5. */
78
+ export declare function seriousOrCritical(findings: readonly AxeFinding[]): AxeFinding[];
79
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Browser plumbing for the site-system journeys: opening a page, finding the header parts by role (with the shared
3
+ * `data-bl` hooks only as a fast path), axe and the in-page helpers.
4
+ */
5
+ import { errors } from '@playwright/test';
6
+ import { SITE_SYSTEM_NOTE_ANNOTATION } from '../playwright-report.js';
7
+ import { pageHelpersSource } from './page-helpers.js';
8
+ /** Define `window.__brandlabSiteSystem` in every document this page loads. */
9
+ export async function installPageHelpers(page) {
10
+ await page.addInitScript({ content: pageHelpersSource() });
11
+ }
12
+ /** Record a note on the running test; brandlab-ready adds it to the check's finding message. */
13
+ export function note(testInfo, description) {
14
+ testInfo.annotations.push({ type: SITE_SYSTEM_NOTE_ANNOTATION, description });
15
+ }
16
+ export function raiseTimeout(testInfo, budget) {
17
+ if (testInfo.timeout !== 0 && testInfo.timeout < budget)
18
+ testInfo.setTimeout(budget);
19
+ }
20
+ /**
21
+ * Open a sampled page and wait for it to settle: the document (DOMContentLoaded, not load: `next start`'s image
22
+ * optimiser can stall first image requests), then load and the network (capped), then hydration where the shared
23
+ * header reports it, then two frames. A navigation timeout is retried once.
24
+ */
25
+ export async function openSitePage(page, path, plan) {
26
+ try {
27
+ await page.goto(path, { waitUntil: 'domcontentloaded', timeout: plan.navigationTimeoutMs });
28
+ }
29
+ catch (error) {
30
+ if (!(error instanceof errors.TimeoutError))
31
+ throw error;
32
+ await page.goto(path, { waitUntil: 'domcontentloaded', timeout: plan.navigationTimeoutMs });
33
+ }
34
+ await settle(page, plan.settleTimeoutMs);
35
+ }
36
+ /** Wait for load and the network (capped), the shared header's hydration flag when it has one, and two frames. */
37
+ export async function settle(page, settleTimeoutMs) {
38
+ const capped = (error) => {
39
+ if (!(error instanceof errors.TimeoutError))
40
+ throw error;
41
+ };
42
+ await page.waitForLoadState('load', { timeout: settleTimeoutMs }).catch(capped);
43
+ await page.waitForLoadState('networkidle', { timeout: settleTimeoutMs }).catch(capped);
44
+ if (await page.locator('[data-bl="header"]').count()) {
45
+ await page.locator('html[data-hydrated="true"], [data-bl="header"][data-hydrated="true"]').first()
46
+ .waitFor({ state: 'attached', timeout: 5_000 }).catch(() => undefined);
47
+ }
48
+ await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(resolve, 150)))));
49
+ }
50
+ /** The site header: the shared header, else the page's banner landmark. Marked for axe as `header`. */
51
+ export async function siteHeader(page) {
52
+ const shared = page.locator('[data-bl="header"]');
53
+ const header = (await shared.count()) > 0 ? shared.first() : page.getByRole('banner').first();
54
+ if (await header.count())
55
+ await header.evaluate((element) => window.__brandlabSiteSystem?.markElement(element, 'header'));
56
+ return header;
57
+ }
58
+ /** The site footer: the shared footer, else the page's contentinfo landmark. Marked for axe as `footer`. */
59
+ export async function siteFooter(page) {
60
+ const shared = page.locator('[data-bl="footer"]');
61
+ const footer = (await shared.count()) > 0 ? shared.first() : page.getByRole('contentinfo').first();
62
+ if (await footer.count())
63
+ await footer.evaluate((element) => window.__brandlabSiteSystem?.markElement(element, 'footer'));
64
+ return footer;
65
+ }
66
+ /** The visible main menu, by its accessible name. Marked for axe as `nav`. */
67
+ export async function mainNavigation(page, plan) {
68
+ const nav = page.getByRole('navigation', { name: plan.navName, exact: true }).filter({ visible: true }).first();
69
+ if ((await nav.count()) === 0)
70
+ return null;
71
+ await nav.evaluate((element) => window.__brandlabSiteSystem?.markElement(element, 'nav'));
72
+ return nav;
73
+ }
74
+ /** The menu's visible disclosure buttons, each with the panel it controls. */
75
+ export async function menuDisclosures(nav) {
76
+ const buttons = nav.locator('button[aria-expanded][aria-controls]').filter({ visible: true });
77
+ const disclosures = [];
78
+ for (let index = 0; index < (await buttons.count()); index += 1) {
79
+ const trigger = buttons.nth(index);
80
+ const panelId = (await trigger.getAttribute('aria-controls'))?.trim().split(/\s+/)[0] ?? '';
81
+ const label = (await trigger.getAttribute('aria-label')) ?? (await trigger.innerText()).trim();
82
+ disclosures.push({ trigger, panelId, label: label || `menu button ${index + 1}` });
83
+ }
84
+ return disclosures;
85
+ }
86
+ /** Menu links hidden at rest: a sign of submenus that a disclosure button should control. */
87
+ export async function hiddenMenuLinks(nav) {
88
+ return nav.evaluate((element) => Array.from(element.querySelectorAll('a[href]')).filter((link) => !window.__brandlabSiteSystem.isRendered(link)).length);
89
+ }
90
+ export function panelLocator(page, panelId) {
91
+ return page.locator(`[id="${panelId.replace(/["\\]/g, '\\$&')}"]`);
92
+ }
93
+ export async function panelOpen(page, disclosure) {
94
+ return page.evaluate((id) => window.__brandlabSiteSystem.isRendered(document.getElementById(id)), disclosure.panelId);
95
+ }
96
+ /** Every disclosure's panel is hidden and every button says so. */
97
+ export async function openPanels(page, disclosures) {
98
+ const open = [];
99
+ for (const disclosure of disclosures) {
100
+ const expanded = (await disclosure.trigger.getAttribute('aria-expanded')) === 'true';
101
+ if (expanded || (await panelOpen(page, disclosure)))
102
+ open.push(disclosure.label);
103
+ }
104
+ return open;
105
+ }
106
+ /** Where to point to hover a disclosure: its item's top-level link, else the button itself. */
107
+ export async function hoverPoint(disclosure) {
108
+ return disclosure.trigger.evaluate((trigger, panelId) => {
109
+ const item = trigger.closest('li') ?? trigger.parentElement ?? trigger;
110
+ const panel = document.getElementById(panelId);
111
+ const link = Array.from(item.querySelectorAll('a[href]')).find((anchor) => !panel?.contains(anchor) && window.__brandlabSiteSystem.isRendered(anchor));
112
+ const rect = (link ?? trigger).getBoundingClientRect();
113
+ return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
114
+ }, disclosure.panelId);
115
+ }
116
+ /** A neutral spot for the pointer: not on a control, the header or the menu. */
117
+ export async function restingPoint(page) {
118
+ const point = await page.evaluate(() => {
119
+ const avoid = Array.from(document.querySelectorAll('[data-brandlab-journey~="header"], [data-brandlab-journey~="nav"]'));
120
+ return window.__brandlabSiteSystem.outsidePoint(avoid);
121
+ });
122
+ const viewport = page.viewportSize() ?? { width: 1440, height: 900 };
123
+ return point ?? { x: 5, y: viewport.height - 5 };
124
+ }
125
+ /** Wait until `condition` holds or `timeoutMs` passes; returns whether it held. */
126
+ export async function waitUntil(condition, timeoutMs, intervalMs = 25) {
127
+ const deadline = Date.now() + timeoutMs;
128
+ for (;;) {
129
+ if (await condition())
130
+ return true;
131
+ if (Date.now() >= deadline)
132
+ return false;
133
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
134
+ }
135
+ }
136
+ /** WCAG 2.2 A and AA, as the prototype's check ran it. */
137
+ export const SITE_SYSTEM_AXE_TAGS = Object.freeze(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']);
138
+ let axeLoader;
139
+ /** `@axe-core/playwright` when the site has it, else `axe-core` injected; either is an optional peer dependency. */
140
+ async function loadAxe() {
141
+ axeLoader ??= (async () => {
142
+ const playwrightAxe = '@axe-core/playwright';
143
+ try {
144
+ const module = await import(playwrightAxe);
145
+ const builder = module.default ?? module.AxeBuilder;
146
+ if (builder)
147
+ return { builder };
148
+ }
149
+ catch { /* not installed: fall back to axe-core */ }
150
+ const axeCore = 'axe-core';
151
+ try {
152
+ const module = await import(axeCore);
153
+ const source = module.default?.source ?? module.source;
154
+ if (source)
155
+ return { source };
156
+ }
157
+ catch { /* not installed either */ }
158
+ return {};
159
+ })();
160
+ return axeLoader;
161
+ }
162
+ function lite(results) {
163
+ return results.map((result) => ({
164
+ id: result.id,
165
+ impact: result.impact ?? null,
166
+ help: result.help,
167
+ targets: result.nodes.slice(0, 5).map((node) => node.target.map(String).join(' ')),
168
+ }));
169
+ }
170
+ /** Run axe over the parts named by `include` selectors, with WCAG tags or named rules. */
171
+ export async function runAxe(page, include, only) {
172
+ const axe = await loadAxe();
173
+ if (axe.builder) {
174
+ const present = [];
175
+ for (const selector of include)
176
+ if (await page.locator(selector).count())
177
+ present.push(selector);
178
+ if (present.length === 0)
179
+ return { violations: [], incomplete: 0 };
180
+ const builder = new axe.builder({ page });
181
+ for (const selector of present)
182
+ builder.include(selector);
183
+ if (only.rules)
184
+ builder.withRules([...only.rules]);
185
+ else
186
+ builder.withTags([...(only.tags ?? SITE_SYSTEM_AXE_TAGS)]);
187
+ const results = await builder.analyze();
188
+ return { violations: lite(results.violations), incomplete: results.incomplete.length };
189
+ }
190
+ if (!axe.source) {
191
+ throw new Error('The site-system journeys need axe: add @axe-core/playwright (or axe-core) to the project\'s devDependencies');
192
+ }
193
+ if (!(await page.evaluate(() => Boolean(window.axe))))
194
+ await page.evaluate(axe.source);
195
+ const results = await page.evaluate(async ({ include, rules, tags }) => {
196
+ const axeRun = window.axe;
197
+ const context = { include: include.filter((selector) => document.querySelector(selector)).map((selector) => [selector]) };
198
+ if (context.include.length === 0)
199
+ return { violations: [], incomplete: 0 };
200
+ const result = await axeRun.run(context, {
201
+ runOnly: rules ? { type: 'rule', values: rules } : { type: 'tag', values: tags },
202
+ resultTypes: ['violations', 'incomplete'],
203
+ });
204
+ return {
205
+ violations: result.violations.map((violation) => ({ id: violation.id, impact: violation.impact ?? null, help: violation.help, nodes: violation.nodes.map((node) => ({ target: node.target })) })),
206
+ incomplete: result.incomplete.length,
207
+ };
208
+ }, { include: [...include], rules: only.rules ? [...only.rules] : null, tags: [...(only.tags ?? SITE_SYSTEM_AXE_TAGS)] });
209
+ return { violations: lite(results.violations), incomplete: results.incomplete };
210
+ }
211
+ export function describeAxe(findings) {
212
+ return findings.map((finding) => `${finding.id} (${finding.impact ?? 'no impact'}): ${finding.help} at ${finding.targets.join(', ')}`);
213
+ }
214
+ /** Serious and critical findings: the bar the prototype set for N5. */
215
+ export function seriousOrCritical(findings) {
216
+ return findings.filter((finding) => finding.impact === 'serious' || finding.impact === 'critical');
217
+ }
218
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../../src/site-system/journeys/browser.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Browser plumbing for the site-system journeys: opening a page, finding the header parts by role (with the shared\n * `data-bl` hooks only as a fast path), axe and the in-page helpers.\n */\nimport { errors, type Locator, type Page, type TestInfo } from '@playwright/test';\nimport type { SiteSystemJourneyPlan } from './plan.js';\nimport { SITE_SYSTEM_NOTE_ANNOTATION } from '../playwright-report.js';\nimport { pageHelpersSource, type SiteSystemPageHelpers } from './page-helpers.js';\n\ndeclare global {\n interface Window {\n __brandlabSiteSystem: SiteSystemPageHelpers;\n }\n}\n\n/** Define `window.__brandlabSiteSystem` in every document this page loads. */\nexport async function installPageHelpers(page: Page): Promise<void> {\n await page.addInitScript({ content: pageHelpersSource() });\n}\n\n/** Record a note on the running test; brandlab-ready adds it to the check's finding message. */\nexport function note(testInfo: TestInfo, description: string): void {\n testInfo.annotations.push({ type: SITE_SYSTEM_NOTE_ANNOTATION, description });\n}\n\nexport function raiseTimeout(testInfo: TestInfo, budget: number): void {\n if (testInfo.timeout !== 0 && testInfo.timeout < budget) testInfo.setTimeout(budget);\n}\n\n/**\n * Open a sampled page and wait for it to settle: the document (DOMContentLoaded, not load: `next start`'s image\n * optimiser can stall first image requests), then load and the network (capped), then hydration where the shared\n * header reports it, then two frames. A navigation timeout is retried once.\n */\nexport async function openSitePage(page: Page, path: string, plan: Pick<SiteSystemJourneyPlan, 'navigationTimeoutMs' | 'settleTimeoutMs'>): Promise<void> {\n try {\n await page.goto(path, { waitUntil: 'domcontentloaded', timeout: plan.navigationTimeoutMs });\n } catch (error) {\n if (!(error instanceof errors.TimeoutError)) throw error;\n await page.goto(path, { waitUntil: 'domcontentloaded', timeout: plan.navigationTimeoutMs });\n }\n await settle(page, plan.settleTimeoutMs);\n}\n\n/** Wait for load and the network (capped), the shared header's hydration flag when it has one, and two frames. */\nexport async function settle(page: Page, settleTimeoutMs: number): Promise<void> {\n const capped = (error: unknown) => {\n if (!(error instanceof errors.TimeoutError)) throw error;\n };\n await page.waitForLoadState('load', { timeout: settleTimeoutMs }).catch(capped);\n await page.waitForLoadState('networkidle', { timeout: settleTimeoutMs }).catch(capped);\n if (await page.locator('[data-bl=\"header\"]').count()) {\n await page.locator('html[data-hydrated=\"true\"], [data-bl=\"header\"][data-hydrated=\"true\"]').first()\n .waitFor({ state: 'attached', timeout: 5_000 }).catch(() => undefined);\n }\n await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(resolve, 150)))));\n}\n\n/** The site header: the shared header, else the page's banner landmark. Marked for axe as `header`. */\nexport async function siteHeader(page: Page): Promise<Locator> {\n const shared = page.locator('[data-bl=\"header\"]');\n const header = (await shared.count()) > 0 ? shared.first() : page.getByRole('banner').first();\n if (await header.count()) await header.evaluate((element) => window.__brandlabSiteSystem?.markElement(element, 'header'));\n return header;\n}\n\n/** The site footer: the shared footer, else the page's contentinfo landmark. Marked for axe as `footer`. */\nexport async function siteFooter(page: Page): Promise<Locator> {\n const shared = page.locator('[data-bl=\"footer\"]');\n const footer = (await shared.count()) > 0 ? shared.first() : page.getByRole('contentinfo').first();\n if (await footer.count()) await footer.evaluate((element) => window.__brandlabSiteSystem?.markElement(element, 'footer'));\n return footer;\n}\n\n/** The visible main menu, by its accessible name. Marked for axe as `nav`. */\nexport async function mainNavigation(page: Page, plan: Pick<SiteSystemJourneyPlan, 'navName'>): Promise<Locator | null> {\n const nav = page.getByRole('navigation', { name: plan.navName, exact: true }).filter({ visible: true }).first();\n if ((await nav.count()) === 0) return null;\n await nav.evaluate((element) => window.__brandlabSiteSystem?.markElement(element, 'nav'));\n return nav;\n}\n\nexport interface Disclosure {\n /** The disclosure button (`button[aria-expanded][aria-controls]`). */\n trigger: Locator;\n /** The id its aria-controls names. */\n panelId: string;\n label: string;\n}\n\n/** The menu's visible disclosure buttons, each with the panel it controls. */\nexport async function menuDisclosures(nav: Locator): Promise<Disclosure[]> {\n const buttons = nav.locator('button[aria-expanded][aria-controls]').filter({ visible: true });\n const disclosures: Disclosure[] = [];\n for (let index = 0; index < (await buttons.count()); index += 1) {\n const trigger = buttons.nth(index);\n const panelId = (await trigger.getAttribute('aria-controls'))?.trim().split(/\\s+/)[0] ?? '';\n const label = (await trigger.getAttribute('aria-label')) ?? (await trigger.innerText()).trim();\n disclosures.push({ trigger, panelId, label: label || `menu button ${index + 1}` });\n }\n return disclosures;\n}\n\n/** Menu links hidden at rest: a sign of submenus that a disclosure button should control. */\nexport async function hiddenMenuLinks(nav: Locator): Promise<number> {\n return nav.evaluate((element) => Array.from(element.querySelectorAll('a[href]')).filter((link) => !window.__brandlabSiteSystem.isRendered(link)).length);\n}\n\nexport function panelLocator(page: Page, panelId: string): Locator {\n return page.locator(`[id=\"${panelId.replace(/[\"\\\\]/g, '\\\\$&')}\"]`);\n}\n\nexport async function panelOpen(page: Page, disclosure: Disclosure): Promise<boolean> {\n return page.evaluate((id) => window.__brandlabSiteSystem.isRendered(document.getElementById(id)), disclosure.panelId);\n}\n\n/** Every disclosure's panel is hidden and every button says so. */\nexport async function openPanels(page: Page, disclosures: readonly Disclosure[]): Promise<string[]> {\n const open: string[] = [];\n for (const disclosure of disclosures) {\n const expanded = (await disclosure.trigger.getAttribute('aria-expanded')) === 'true';\n if (expanded || (await panelOpen(page, disclosure))) open.push(disclosure.label);\n }\n return open;\n}\n\n/** Where to point to hover a disclosure: its item's top-level link, else the button itself. */\nexport async function hoverPoint(disclosure: Disclosure): Promise<{ x: number; y: number }> {\n return disclosure.trigger.evaluate((trigger, panelId) => {\n const item = trigger.closest('li') ?? trigger.parentElement ?? trigger;\n const panel = document.getElementById(panelId);\n const link = Array.from(item.querySelectorAll('a[href]')).find((anchor) => !panel?.contains(anchor) && window.__brandlabSiteSystem.isRendered(anchor));\n const rect = (link ?? trigger).getBoundingClientRect();\n return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };\n }, disclosure.panelId);\n}\n\n/** A neutral spot for the pointer: not on a control, the header or the menu. */\nexport async function restingPoint(page: Page): Promise<{ x: number; y: number }> {\n const point = await page.evaluate(() => {\n const avoid = Array.from(document.querySelectorAll('[data-brandlab-journey~=\"header\"], [data-brandlab-journey~=\"nav\"]'));\n return window.__brandlabSiteSystem.outsidePoint(avoid);\n });\n const viewport = page.viewportSize() ?? { width: 1440, height: 900 };\n return point ?? { x: 5, y: viewport.height - 5 };\n}\n\n/** Wait until `condition` holds or `timeoutMs` passes; returns whether it held. */\nexport async function waitUntil(condition: () => Promise<boolean>, timeoutMs: number, intervalMs = 25): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (await condition()) return true;\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\nexport interface AxeFinding {\n id: string;\n impact: string | null;\n help: string;\n targets: string[];\n}\n\nexport interface AxeOutcome {\n violations: AxeFinding[];\n incomplete: number;\n}\n\n/** WCAG 2.2 A and AA, as the prototype's check ran it. */\nexport const SITE_SYSTEM_AXE_TAGS = Object.freeze(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']);\n\ntype AxeBuilderClass = new (options: { page: Page }) => {\n include(selector: string): unknown;\n withTags(tags: string[]): unknown;\n withRules(rules: string[]): unknown;\n analyze(): Promise<{ violations: AxeRawResult[]; incomplete: unknown[] }>;\n};\ninterface AxeRawResult { id: string; impact?: string | null; help: string; nodes: { target: unknown[] }[] }\n\nlet axeLoader: Promise<{ builder?: AxeBuilderClass; source?: string }> | undefined;\n\n/** `@axe-core/playwright` when the site has it, else `axe-core` injected; either is an optional peer dependency. */\nasync function loadAxe(): Promise<{ builder?: AxeBuilderClass; source?: string }> {\n axeLoader ??= (async () => {\n const playwrightAxe = '@axe-core/playwright';\n try {\n const module = await import(playwrightAxe) as { default?: AxeBuilderClass; AxeBuilder?: AxeBuilderClass };\n const builder = module.default ?? module.AxeBuilder;\n if (builder) return { builder };\n } catch { /* not installed: fall back to axe-core */ }\n const axeCore = 'axe-core';\n try {\n const module = await import(axeCore) as { default?: { source?: string }; source?: string };\n const source = module.default?.source ?? module.source;\n if (source) return { source };\n } catch { /* not installed either */ }\n return {};\n })();\n return axeLoader;\n}\n\nfunction lite(results: readonly AxeRawResult[]): AxeFinding[] {\n return results.map((result) => ({\n id: result.id,\n impact: result.impact ?? null,\n help: result.help,\n targets: result.nodes.slice(0, 5).map((node) => node.target.map(String).join(' ')),\n }));\n}\n\n/** Run axe over the parts named by `include` selectors, with WCAG tags or named rules. */\nexport async function runAxe(page: Page, include: readonly string[], only: { tags?: readonly string[]; rules?: readonly string[] }): Promise<AxeOutcome> {\n const axe = await loadAxe();\n if (axe.builder) {\n const present: string[] = [];\n for (const selector of include) if (await page.locator(selector).count()) present.push(selector);\n if (present.length === 0) return { violations: [], incomplete: 0 };\n const builder = new axe.builder({ page });\n for (const selector of present) builder.include(selector);\n if (only.rules) builder.withRules([...only.rules]);\n else builder.withTags([...(only.tags ?? SITE_SYSTEM_AXE_TAGS)]);\n const results = await builder.analyze();\n return { violations: lite(results.violations), incomplete: results.incomplete.length };\n }\n if (!axe.source) {\n throw new Error('The site-system journeys need axe: add @axe-core/playwright (or axe-core) to the project\\'s devDependencies');\n }\n if (!(await page.evaluate(() => Boolean((window as unknown as { axe?: unknown }).axe)))) await page.evaluate(axe.source);\n const results = await page.evaluate(async ({ include, rules, tags }) => {\n const axeRun = (window as unknown as { axe: { run(context: unknown, options: unknown): Promise<{ violations: AxeRawResult[]; incomplete: unknown[] }> } }).axe;\n const context = { include: include.filter((selector) => document.querySelector(selector)).map((selector) => [selector]) };\n if (context.include.length === 0) return { violations: [], incomplete: 0 };\n const result = await axeRun.run(context, {\n runOnly: rules ? { type: 'rule', values: rules } : { type: 'tag', values: tags },\n resultTypes: ['violations', 'incomplete'],\n });\n return {\n violations: result.violations.map((violation) => ({ id: violation.id, impact: violation.impact ?? null, help: violation.help, nodes: violation.nodes.map((node) => ({ target: node.target })) })),\n incomplete: result.incomplete.length,\n };\n }, { include: [...include], rules: only.rules ? [...only.rules] : null, tags: [...(only.tags ?? SITE_SYSTEM_AXE_TAGS)] });\n return { violations: lite(results.violations), incomplete: results.incomplete };\n}\n\nexport function describeAxe(findings: readonly AxeFinding[]): string[] {\n return findings.map((finding) => `${finding.id} (${finding.impact ?? 'no impact'}): ${finding.help} at ${finding.targets.join(', ')}`);\n}\n\n/** Serious and critical findings: the bar the prototype set for N5. */\nexport function seriousOrCritical(findings: readonly AxeFinding[]): AxeFinding[] {\n return findings.filter((finding) => finding.impact === 'serious' || finding.impact === 'critical');\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import { type SiteSystemJourneyPlan } from './plan.js';
2
+ export declare function defineConversionJourneys(plan: SiteSystemJourneyPlan, runs: ReadonlySet<string>): void;
3
+ //# sourceMappingURL=conversion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversion.d.ts","sourceRoot":"","sources":["../../../src/site-system/journeys/conversion.ts"],"names":[],"mappings":"AAWA,OAAO,EAA6F,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAMlJ,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CA0HrG"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * C1 and C4. C1 (enquiry tier and above): on each money page the H1, a call action and a quote action show in the
3
+ * first 390 x 844 screen, and the enquiry form shows in the first 1440 x 900 screen. C4: every phone tap reports an
4
+ * allowed placement. Where the build renders the Google tag (a test measurement ID), each visible phone link is
5
+ * tapped and its `phone_link_click` must carry the placement the link declares; without the tag the declared
6
+ * placements are checked and the run says so (the analytics journey fails a build without its tag).
7
+ */
8
+ import { expect, test } from '@playwright/test';
9
+ import { analyticsEvents, recordAnalyticsCommands } from '../../analytics-e2e.js';
10
+ import { note, openSitePage, raiseTimeout, siteHeader, waitUntil } from './browser.js';
11
+ import { SITE_SYSTEM_PHONE_PLACEMENTS } from './page-helpers.js';
12
+ import { SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT, siteSystemJourneyTitle as title } from './plan.js';
13
+ import { callBarLinks } from './shell.js';
14
+ /** The shared listener ignores a second tap on the same number within 1.2 s; taps wait this long apart. */
15
+ const PHONE_TAP_GAP_MS = 1_500;
16
+ export function defineConversionJourneys(plan, runs) {
17
+ const quote = { source: plan.quoteName.source, flags: plan.quoteName.flags };
18
+ if (runs.has('C1')) {
19
+ test.describe('money pages', () => {
20
+ if (plan.problems.C1) {
21
+ const problem = plan.problems.C1;
22
+ test(title('C1', 'the money pages are named'), () => {
23
+ expect([problem]).toEqual([]);
24
+ });
25
+ }
26
+ for (const path of plan.money) {
27
+ test(title('C1', `${path} shows the H1, a call and a quote action in the first phone screen and the form in the first desktop screen`), async ({ page }) => {
28
+ const problems = [];
29
+ await page.setViewportSize({ ...SITE_SYSTEM_PHONE_VIEWPORT });
30
+ await openSitePage(page, path, plan);
31
+ const phone = await page.evaluate((name) => {
32
+ const helpers = window.__brandlabSiteSystem;
33
+ window.scrollTo({ top: 0, behavior: 'instant' });
34
+ const inView = (element) => {
35
+ const rect = element.getBoundingClientRect();
36
+ return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
37
+ };
38
+ const heading = Array.from(document.querySelectorAll('h1')).find(helpers.isRendered);
39
+ const headingTop = heading ? heading.getBoundingClientRect().top : null;
40
+ return {
41
+ heading: heading !== undefined && headingTop !== null && headingTop >= 0 && headingTop < window.innerHeight,
42
+ call: Array.from(document.querySelectorAll('a[href^="tel:"]')).some((link) => helpers.isRendered(link) && inView(link)),
43
+ quote: helpers.quoteActions(document.body, name).some(inView),
44
+ };
45
+ }, quote);
46
+ if (!phone.heading)
47
+ problems.push('390 x 844: the H1 does not start in the first screen');
48
+ if (!phone.call)
49
+ problems.push('390 x 844: no call action in the first screen');
50
+ if (!phone.quote)
51
+ problems.push('390 x 844: no quote action in the first screen');
52
+ await page.setViewportSize({ ...SITE_SYSTEM_DESKTOP_VIEWPORT });
53
+ await openSitePage(page, path, plan);
54
+ const form = await page.evaluate(() => {
55
+ window.scrollTo({ top: 0, behavior: 'instant' });
56
+ const forms = Array.from(document.querySelectorAll('form')).filter((element) => window.__brandlabSiteSystem.isRendered(element)
57
+ && element.querySelector('input[type="tel"], input[type="email"], textarea') !== null);
58
+ const heading = Array.from(document.querySelectorAll('h1')).find(window.__brandlabSiteSystem.isRendered);
59
+ const first = [heading?.closest('section, [data-bl-section]'), document.querySelector('main')?.firstElementChild]
60
+ .filter((element) => Boolean(element) && element.tagName !== 'MAIN');
61
+ return {
62
+ inScreen: forms.some((element) => element.getBoundingClientRect().top < window.innerHeight),
63
+ inFirstSection: forms.some((element) => first.some((section) => section.contains(element))),
64
+ };
65
+ });
66
+ if (!form.inScreen)
67
+ problems.push('1440 x 900: no enquiry form (with a phone, email or message field) in the first screen');
68
+ else if (!form.inFirstSection)
69
+ problems.push('the enquiry form is not in the hero or the first section of <main>');
70
+ expect(problems).toEqual([]);
71
+ });
72
+ }
73
+ });
74
+ }
75
+ if (!runs.has('C4'))
76
+ return;
77
+ test.describe('phone placements', () => {
78
+ for (const path of plan.sampled) {
79
+ test(title('C4', `phone taps on ${path} report allowed placements`), async ({ page }, testInfo) => {
80
+ raiseTimeout(testInfo, 180_000);
81
+ const commands = await recordAnalyticsCommands(page);
82
+ // Keep the page in place; the shared listener still sees the tap.
83
+ await page.addInitScript(() => window.addEventListener('click', (event) => {
84
+ if (event.target?.closest?.('a[href^="tel:"]'))
85
+ event.preventDefault();
86
+ }, true));
87
+ const problems = [];
88
+ let analytics;
89
+ let taps = 0;
90
+ let declared = 0;
91
+ const tap = async (mark, link, where) => {
92
+ declared += 1;
93
+ if (link.invalid)
94
+ problems.push(`${where}: ${link.description} declares data-phone-location "${link.invalid}", which is not an allowed placement (${SITE_SYSTEM_PHONE_PLACEMENTS.join(', ')}); its taps are reported as ${link.placement}`);
95
+ if (!analytics)
96
+ return;
97
+ const before = analyticsEvents(commands, 'phone_link_click').length;
98
+ await page.locator(`[data-brandlab-journey~="${mark}"]`).first().click();
99
+ await waitUntil(async () => analyticsEvents(commands, 'phone_link_click').length > before, 2_000);
100
+ const sent = analyticsEvents(commands, 'phone_link_click').slice(before);
101
+ taps += 1;
102
+ if (sent.length !== 1)
103
+ problems.push(`${where}: a tap on ${link.description} sent ${sent.length} phone_link_click event(s)`);
104
+ const placement = String(sent[0]?.params.placement ?? '');
105
+ if (sent.length > 0 && !SITE_SYSTEM_PHONE_PLACEMENTS.includes(placement))
106
+ problems.push(`${where}: a tap on ${link.description} reported placement "${placement}", which is not allowed`);
107
+ else if (sent.length > 0 && placement !== link.placement)
108
+ problems.push(`${where}: a tap on ${link.description} reported placement "${placement}" (the link declares ${link.placement})`);
109
+ await page.waitForTimeout(PHONE_TAP_GAP_MS);
110
+ };
111
+ for (const viewport of [SITE_SYSTEM_DESKTOP_VIEWPORT, SITE_SYSTEM_PHONE_VIEWPORT]) {
112
+ await page.setViewportSize({ ...viewport });
113
+ await openSitePage(page, path, plan);
114
+ await siteHeader(page);
115
+ analytics ??= await waitUntil(async () => commands.some((command) => command[0] === 'js'), 3_000);
116
+ const where = `${viewport.width} px`;
117
+ const links = await page.evaluate(() => {
118
+ const helpers = window.__brandlabSiteSystem;
119
+ return Array.from(document.querySelectorAll('a[href^="tel:"]')).filter(helpers.isRendered).map((link, index) => {
120
+ helpers.markElement(link, `tel-${index}`);
121
+ return { mark: `tel-${index}`, description: helpers.describeElement(link), ...helpers.phonePlacement(link) };
122
+ });
123
+ });
124
+ for (const link of links)
125
+ await tap(link.mark, link, where);
126
+ if (viewport.width < 1024) {
127
+ const bar = await callBarLinks(page);
128
+ if (bar) {
129
+ const marks = await page.evaluate(() => {
130
+ const helpers = window.__brandlabSiteSystem;
131
+ const root = document.querySelector('[data-brandlab-journey~="call-bar"]');
132
+ return Array.from(root?.querySelectorAll('a[href^="tel:"]') ?? []).filter(helpers.isRendered).map((link, index) => {
133
+ helpers.markElement(link, `bar-tel-${index}`);
134
+ return `bar-tel-${index}`;
135
+ });
136
+ });
137
+ for (const [index, mark] of marks.entries())
138
+ await tap(mark, bar[index], `${where} call bar`);
139
+ }
140
+ }
141
+ }
142
+ expect(declared, `${path} shows no phone link`).toBeGreaterThan(0);
143
+ if (!analytics)
144
+ note(testInfo, `${path}: the build sends no analytics (no Google tag ran), so the declared placements were checked without tapping; build the e2e target with a test NEXT_PUBLIC_GA_MEASUREMENT_ID to check the events`);
145
+ else
146
+ note(testInfo, `${path}: ${taps} phone tap(s) checked against their phone_link_click events`);
147
+ expect(problems).toEqual([]);
148
+ });
149
+ }
150
+ });
151
+ }
152
+ //# sourceMappingURL=conversion.js.map