[{"data":1,"prerenderedAt":1661},["ShallowReactive",2],{"navigation":3,"\u002Fdeployment\u002Fbrowser-tests":561,"\u002Fdeployment\u002Fbrowser-tests-surround":1656},[4,22,64,89,219,507],{"title":5,"path":6,"stem":7,"children":8},"Introduction","\u002Fgetting-started","1.getting-started\u002F1.index",[9,10,14,18],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation",{"title":15,"path":16,"stem":17},"Key Ideas","\u002Fgetting-started\u002Fkey-ideas","1.getting-started\u002F3.key-ideas",{"title":19,"path":20,"stem":21},"Releases","\u002Fgetting-started\u002Freleases","1.getting-started\u002F4.releases",{"title":23,"path":24,"stem":25,"children":26},"Overview","\u002Fcore-concepts","2.core-concepts\u002F0.index",[27,28,32,36,40,44,48,52,56,60],{"title":23,"path":24,"stem":25},{"title":29,"path":30,"stem":31},"How It All Works","\u002Fcore-concepts\u002Farchitecture","2.core-concepts\u002F1.architecture",{"title":33,"path":34,"stem":35},"The Data Model","\u002Fcore-concepts\u002Fthe-data-model","2.core-concepts\u002F2.the-data-model",{"title":37,"path":38,"stem":39},"Layouts & Pages","\u002Fcore-concepts\u002Flayouts-and-pages","2.core-concepts\u002F3.layouts-and-pages",{"title":41,"path":42,"stem":43},"Dynamic Pages","\u002Fcore-concepts\u002Fdynamic-pages","2.core-concepts\u002F4.dynamic-pages",{"title":45,"path":46,"stem":47},"Components","\u002Fcore-concepts\u002Fcomponents","2.core-concepts\u002F5.components",{"title":49,"path":50,"stem":51},"Draft & Publish Workflow","\u002Fcore-concepts\u002Fdraft-and-publish","2.core-concepts\u002F6.draft-and-publish",{"title":53,"path":54,"stem":55},"The Admin Panel","\u002Fcore-concepts\u002Fadmin-panel","2.core-concepts\u002F7.admin-panel",{"title":57,"path":58,"stem":59},"Caching","\u002Fcore-concepts\u002Fcaching","2.core-concepts\u002F8.caching",{"title":61,"path":62,"stem":63},"Glossary","\u002Fcore-concepts\u002Fglossary","2.core-concepts\u002F9.glossary",{"title":23,"path":65,"stem":66,"children":67},"\u002Fguides","3.guides\u002F0.index",[68,69,73,77,81,85],{"title":23,"path":65,"stem":66},{"title":70,"path":71,"stem":72},"Your First Layout","\u002Fguides\u002Fyour-first-layout","3.guides\u002F1.your-first-layout",{"title":74,"path":75,"stem":76},"Your First Page Template","\u002Fguides\u002Fyour-first-page-template","3.guides\u002F2.your-first-page-template",{"title":78,"path":79,"stem":80},"Your First Component","\u002Fguides\u002Fyour-first-component","3.guides\u002F3.your-first-component",{"title":82,"path":83,"stem":84},"Alternative UI Variants","\u002Fguides\u002Falternative-ui-variants","3.guides\u002F4.alternative-ui-variants",{"title":86,"path":87,"stem":88},"Your First Dynamic Page","\u002Fguides\u002Fyour-first-dynamic-page","3.guides\u002F5.your-first-dynamic-page",{"title":23,"path":90,"stem":91,"children":92},"\u002Fapi","4.api\u002F0.index",[93,94,98,102,120,162,166,170,199,203,207,211,215],{"title":23,"path":90,"stem":91},{"title":95,"path":96,"stem":97},"Bundle Setup","\u002Fapi\u002Fbundle-setup","4.api\u002F02.bundle-setup",{"title":99,"path":100,"stem":101},"Configuration Reference","\u002Fapi\u002Fconfiguration","4.api\u002F03.configuration",{"title":103,"path":104,"stem":105,"children":106,"page":119},"Users","\u002Fapi\u002Fusers","4.api\u002F04.users",[107,111,115],{"title":108,"path":109,"stem":110},"Authentication","\u002Fapi\u002Fusers\u002Fauthentication","4.api\u002F04.users\u002F1.authentication",{"title":112,"path":113,"stem":114},"User Emails","\u002Fapi\u002Fusers\u002Fuser-emails","4.api\u002F04.users\u002F2.user-emails",{"title":116,"path":117,"stem":118},"Access Control","\u002Fapi\u002Fusers\u002Faccess-control","4.api\u002F04.users\u002F3.access-control",false,{"title":45,"path":121,"stem":122,"children":123,"page":119},"\u002Fapi\u002Fcomponents","4.api\u002F05.components",[124,128,149],{"title":125,"path":126,"stem":127},"Creating Components","\u002Fapi\u002Fcomponents\u002Fcreating-components","4.api\u002F05.components\u002F1.creating-components",{"title":129,"path":130,"stem":131,"children":132,"page":119},"Annotations","\u002Fapi\u002Fcomponents\u002Fannotations","4.api\u002F05.components\u002F2.annotations",[133,137,141,145],{"title":134,"path":135,"stem":136},"Publishable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fpublishable","4.api\u002F05.components\u002F2.annotations\u002F1.publishable",{"title":138,"path":139,"stem":140},"Uploadable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fuploadable","4.api\u002F05.components\u002F2.annotations\u002F2.uploadable",{"title":142,"path":143,"stem":144},"Timestamped","\u002Fapi\u002Fcomponents\u002Fannotations\u002Ftimestamped","4.api\u002F05.components\u002F2.annotations\u002F3.timestamped",{"title":146,"path":147,"stem":148},"Explicit Allow Only","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fexplicit-allow-only","4.api\u002F05.components\u002F2.annotations\u002F4.explicit-allow-only",{"title":150,"path":151,"stem":152,"children":153,"page":119},"Built Ins","\u002Fapi\u002Fcomponents\u002Fbuilt-ins","4.api\u002F05.components\u002F3.built-ins",[154,158],{"title":155,"path":156,"stem":157},"Collection Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fcollection-component","4.api\u002F05.components\u002F3.built-ins\u002F1.collection-component",{"title":159,"path":160,"stem":161},"Form Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fform-component","4.api\u002F05.components\u002F3.built-ins\u002F2.form-component",{"title":163,"path":164,"stem":165},"Page Data & Nested Pages","\u002Fapi\u002Fdynamic-pages","4.api\u002F06.dynamic-pages",{"title":167,"path":168,"stem":169},"Routes","\u002Fapi\u002Froutes","4.api\u002F07.routes",{"title":171,"path":172,"stem":173,"children":174,"page":119},"Data Fixtures","\u002Fapi\u002Fdata-fixtures","4.api\u002F08.data-fixtures",[175,179,183,187,191,195],{"title":176,"path":177,"stem":178},"Getting Started with Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgetting-started","4.api\u002F08.data-fixtures\u002F1.getting-started",{"title":180,"path":181,"stem":182},"Writing a Scaffold","\u002Fapi\u002Fdata-fixtures\u002Fwriting-a-scaffold","4.api\u002F08.data-fixtures\u002F2.writing-a-scaffold",{"title":184,"path":185,"stem":186},"Builder Reference","\u002Fapi\u002Fdata-fixtures\u002Fbuilder-reference","4.api\u002F08.data-fixtures\u002F3.builder-reference",{"title":188,"path":189,"stem":190},"Seeding Content","\u002Fapi\u002Fdata-fixtures\u002Fseeding-content","4.api\u002F08.data-fixtures\u002F4.seeding-content",{"title":192,"path":193,"stem":194},"Appending & Multiple Scaffolds","\u002Fapi\u002Fdata-fixtures\u002Fappending","4.api\u002F08.data-fixtures\u002F5.appending",{"title":196,"path":197,"stem":198},"Generating Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgenerating-fixtures","4.api\u002F08.data-fixtures\u002F6.generating-fixtures",{"title":200,"path":201,"stem":202},"Real-Time (Mercure)","\u002Fapi\u002Freal-time","4.api\u002F09.real-time",{"title":204,"path":205,"stem":206},"HTTP Caching","\u002Fapi\u002Fhttp-caching","4.api\u002F10.http-caching",{"title":208,"path":209,"stem":210},"Orphaned Data","\u002Fapi\u002Forphaned-data","4.api\u002F11.orphaned-data",{"title":212,"path":213,"stem":214},"Console Commands","\u002Fapi\u002Fconsole-commands","4.api\u002F12.console-commands",{"title":216,"path":217,"stem":218},"Debugging & Errors","\u002Fapi\u002Fdebugging","4.api\u002F13.debugging",{"title":23,"path":220,"stem":221,"children":222},"\u002Fnuxt-module","5.nuxt-module\u002F0.index",[223,224,228,241,261,290,311,434,468,471,499,503],{"title":23,"path":220,"stem":221},{"title":225,"path":226,"stem":227},"Module Setup","\u002Fnuxt-module\u002Fmodule-setup","5.nuxt-module\u002F01.module-setup",{"title":229,"path":230,"stem":231,"children":232,"page":119},"Configuration","\u002Fnuxt-module\u002Fconfiguration","5.nuxt-module\u002F02.configuration",[233,237],{"title":234,"path":235,"stem":236},"Nuxt Config","\u002Fnuxt-module\u002Fconfiguration\u002Fnuxt-config","5.nuxt-module\u002F02.configuration\u002F1.nuxt-config",{"title":238,"path":239,"stem":240},"Site Config & SEO","\u002Fnuxt-module\u002Fconfiguration\u002Fsite-config-and-seo","5.nuxt-module\u002F02.configuration\u002F2.site-config-and-seo",{"title":242,"path":243,"stem":244,"children":245,"page":119},"Building Your Ui","\u002Fnuxt-module\u002Fbuilding-your-ui","5.nuxt-module\u002F03.building-your-ui",[246,250,254,257],{"title":247,"path":248,"stem":249},"Layouts","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-layouts","5.nuxt-module\u002F03.building-your-ui\u002F1.creating-layouts",{"title":251,"path":252,"stem":253},"Page Templates","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-page-templates","5.nuxt-module\u002F03.building-your-ui\u002F2.creating-page-templates",{"title":45,"path":255,"stem":256},"\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-components","5.nuxt-module\u002F03.building-your-ui\u002F3.creating-components",{"title":258,"path":259,"stem":260},"CLI Generator","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcwa-cli","5.nuxt-module\u002F03.building-your-ui\u002F4.cwa-cli",{"title":262,"path":263,"stem":264,"children":265,"page":119},"Component Helpers","\u002Fnuxt-module\u002Fcomponent-helpers","5.nuxt-module\u002F04.component-helpers",[266,270,274,278,282,286],{"title":267,"path":268,"stem":269},"Images & Media","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fimages-and-uploads","5.nuxt-module\u002F04.component-helpers\u002F1.images-and-uploads",{"title":271,"path":272,"stem":273},"Collections & Pagination","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fcollections-and-pagination","5.nuxt-module\u002F04.component-helpers\u002F2.collections-and-pagination",{"title":275,"path":276,"stem":277},"HTML Content","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fhtml-content","5.nuxt-module\u002F04.component-helpers\u002F3.html-content",{"title":279,"path":280,"stem":281},"Rich-Text Editor","\u002Fnuxt-module\u002Fcomponent-helpers\u002Frich-text-editor","5.nuxt-module\u002F04.component-helpers\u002F4.rich-text-editor",{"title":283,"path":284,"stem":285},"Real-Time Updates","\u002Fnuxt-module\u002Fcomponent-helpers\u002Freal-time-updates","5.nuxt-module\u002F04.component-helpers\u002F5.real-time-updates",{"title":287,"path":288,"stem":289},"Forms","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fforms","5.nuxt-module\u002F04.component-helpers\u002F6.forms",{"title":291,"path":292,"stem":293,"children":294,"page":119},"Cwa Components","\u002Fnuxt-module\u002Fcwa-components","5.nuxt-module\u002F05.cwa-components",[295,299,303,307],{"title":296,"path":297,"stem":298},"\u003CCwaComponentGroup \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-component-group","5.nuxt-module\u002F05.cwa-components\u002F1.cwa-component-group",{"title":300,"path":301,"stem":302},"\u003CCwaPage \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-page","5.nuxt-module\u002F05.cwa-components\u002F2.cwa-page",{"title":304,"path":305,"stem":306},"\u003CCwaLink \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-link","5.nuxt-module\u002F05.cwa-components\u002F3.cwa-link",{"title":308,"path":309,"stem":310},"\u003CCwaDefaultLayout \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-default-layout","5.nuxt-module\u002F05.cwa-components\u002F5.cwa-default-layout",{"title":312,"path":313,"stem":314,"children":315,"page":119},"Composables","\u002Fnuxt-module\u002Fcomposables","5.nuxt-module\u002F06.composables",[316,319,327,352,372,389,409],{"title":23,"path":317,"stem":318},"\u002Fnuxt-module\u002Fcomposables\u002Foverview","5.nuxt-module\u002F06.composables\u002F0.overview",{"title":320,"path":321,"stem":322,"children":323,"page":119},"Layout","\u002Fnuxt-module\u002Fcomposables\u002Flayout","5.nuxt-module\u002F06.composables\u002F1.layout",[324],{"title":320,"path":325,"stem":326},"\u002Fnuxt-module\u002Fcomposables\u002Flayout\u002Fuse-cwa-layout","5.nuxt-module\u002F06.composables\u002F1.layout\u002F1.use-cwa-layout",{"title":328,"path":329,"stem":330,"children":331,"page":119},"Component","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent","5.nuxt-module\u002F06.composables\u002F2.component",[332,336,340,344,348],{"title":333,"path":334,"stem":335},"Component (recommended)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-component","5.nuxt-module\u002F06.composables\u002F2.component\u002F1.use-cwa-component",{"title":337,"path":338,"stem":339},"Resource","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F2.use-cwa-resource",{"title":341,"path":342,"stem":343},"File Field","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-file-field","5.nuxt-module\u002F06.composables\u002F2.component\u002F3.use-cwa-file-field",{"title":345,"path":346,"stem":347},"Collection Pagination","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-pagination","5.nuxt-module\u002F06.composables\u002F2.component\u002F4.use-cwa-collection-pagination",{"title":349,"path":350,"stem":351},"Collection Resource (deprecated)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F9.use-cwa-collection-resource",{"title":353,"path":354,"stem":355,"children":356,"page":119},"Form","\u002Fnuxt-module\u002Fcomposables\u002Fform","5.nuxt-module\u002F06.composables\u002F3.form",[357,360,364,368],{"title":353,"path":358,"stem":359},"\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form","5.nuxt-module\u002F06.composables\u002F3.form\u002F1.use-cwa-form",{"title":361,"path":362,"stem":363},"Form Input","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-input","5.nuxt-module\u002F06.composables\u002F3.form\u002F2.use-cwa-form-input",{"title":365,"path":366,"stem":367},"Form Repeated","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-repeated","5.nuxt-module\u002F06.composables\u002F3.form\u002F3.use-cwa-form-repeated",{"title":369,"path":370,"stem":371},"Form Collection","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-collection","5.nuxt-module\u002F06.composables\u002F3.form\u002F4.use-cwa-form-collection",{"title":373,"path":374,"stem":375,"children":376,"page":119},"Admin Manager","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager","5.nuxt-module\u002F06.composables\u002F4.admin-manager",[377,381,385],{"title":378,"path":379,"stem":380},"Manager Tab","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-manager-tab","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F1.use-cwa-resource-manager-tab",{"title":382,"path":383,"stem":384},"Resource Model","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-model","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F2.use-cwa-resource-model",{"title":386,"path":387,"stem":388},"Resource Upload","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-upload","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F3.use-cwa-resource-upload",{"title":390,"path":391,"stem":392,"children":393,"page":119},"Utilities","\u002Fnuxt-module\u002Fcomposables\u002Futilities","5.nuxt-module\u002F06.composables\u002F5.utilities",[394,398,402,406],{"title":395,"path":396,"stem":397},"Resource Endpoint","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-endpoint","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F1.use-cwa-resource-endpoint",{"title":399,"path":400,"stem":401},"Query Model","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-query-bound-model","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F2.use-query-bound-model",{"title":403,"path":404,"stem":405},"Resource Route","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-route","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F3.use-cwa-resource-route",{"title":275,"path":407,"stem":408},"\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-html-content","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F4.use-html-content",{"title":410,"path":411,"stem":412,"children":413,"page":119},"Auth","\u002Fnuxt-module\u002Fcomposables\u002Fauth","5.nuxt-module\u002F06.composables\u002F6.auth",[414,418,422,426,430],{"title":415,"path":416,"stem":417},"Login","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-login","5.nuxt-module\u002F06.composables\u002F6.auth\u002F1.use-login",{"title":419,"path":420,"stem":421},"Forgot Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-forgot-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F2.use-forgot-password",{"title":423,"path":424,"stem":425},"Reset Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-reset-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F3.use-reset-password",{"title":427,"path":428,"stem":429},"Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F4.use-verify-email",{"title":431,"path":432,"stem":433},"Resend Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-resend-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F5.use-resend-verify-email",{"title":435,"path":436,"stem":437,"children":438,"page":119},"Cwa Api","\u002Fnuxt-module\u002Fcwa-api","5.nuxt-module\u002F07.cwa-api",[439,442,446,450,453,456,460,464],{"title":23,"path":440,"stem":441},"\u002Fnuxt-module\u002Fcwa-api\u002Foverview","5.nuxt-module\u002F07.cwa-api\u002F1.overview",{"title":443,"path":444,"stem":445},"Resources","\u002Fnuxt-module\u002Fcwa-api\u002Fresources","5.nuxt-module\u002F07.cwa-api\u002F2.resources",{"title":447,"path":448,"stem":449},"Resources Manager","\u002Fnuxt-module\u002Fcwa-api\u002Fresources-manager","5.nuxt-module\u002F07.cwa-api\u002F3.resources-manager",{"title":410,"path":451,"stem":452},"\u002Fnuxt-module\u002Fcwa-api\u002Fauth","5.nuxt-module\u002F07.cwa-api\u002F4.auth",{"title":287,"path":454,"stem":455},"\u002Fnuxt-module\u002Fcwa-api\u002Fforms","5.nuxt-module\u002F07.cwa-api\u002F5.forms",{"title":457,"path":458,"stem":459},"Admin","\u002Fnuxt-module\u002Fcwa-api\u002Fadmin","5.nuxt-module\u002F07.cwa-api\u002F6.admin",{"title":461,"path":462,"stem":463},"Site Config","\u002Fnuxt-module\u002Fcwa-api\u002Fsite-config","5.nuxt-module\u002F07.cwa-api\u002F7.site-config",{"title":465,"path":466,"stem":467},"Orphaned Resources","\u002Fnuxt-module\u002Fcwa-api\u002Forphaned-resources","5.nuxt-module\u002F07.cwa-api\u002F8.orphaned-resources",{"title":108,"path":469,"stem":470},"\u002Fnuxt-module\u002Fauthentication","5.nuxt-module\u002F08.authentication",{"title":472,"path":473,"stem":474,"children":475,"page":119},"Cwa Layer","\u002Fnuxt-module\u002Fcwa-layer","5.nuxt-module\u002F9.cwa-layer",[476,479,483,487,491,495],{"title":23,"path":477,"stem":478},"\u002Fnuxt-module\u002Fcwa-layer\u002Foverview","5.nuxt-module\u002F9.cwa-layer\u002F1.overview",{"title":480,"path":481,"stem":482},"Auth Pages","\u002Fnuxt-module\u002Fcwa-layer\u002Fauth-pages","5.nuxt-module\u002F9.cwa-layer\u002F2.auth-pages",{"title":484,"path":485,"stem":486},"Admin Panel","\u002Fnuxt-module\u002Fcwa-layer\u002Fadmin-panel","5.nuxt-module\u002F9.cwa-layer\u002F3.admin-panel",{"title":488,"path":489,"stem":490},"Styling","\u002Fnuxt-module\u002Fcwa-layer\u002Fstyling","5.nuxt-module\u002F9.cwa-layer\u002F4.styling",{"title":492,"path":493,"stem":494},"Date Pickers","\u002Fnuxt-module\u002Fcwa-layer\u002Fdate-pickers","5.nuxt-module\u002F9.cwa-layer\u002F5.date-pickers",{"title":496,"path":497,"stem":498},"Maintenance","\u002Fnuxt-module\u002Fcwa-layer\u002Fmaintenance","5.nuxt-module\u002F9.cwa-layer\u002F6.maintenance",{"title":500,"path":501,"stem":502},"PWA & Offline","\u002Fnuxt-module\u002Fpwa","5.nuxt-module\u002F91.pwa",{"title":504,"path":505,"stem":506},"Page Caching","\u002Fnuxt-module\u002Fpage-caching","5.nuxt-module\u002F92.page-caching",{"title":508,"path":509,"stem":510,"children":511},"Deploying a CWA Site","\u002Fdeployment","6.deployment\u002F0.index",[512,513,517,521,525,529,533,537,541,545,549,553,557],{"title":508,"path":509,"stem":510},{"title":514,"path":515,"stem":516},"Docker","\u002Fdeployment\u002Fdocker","6.deployment\u002F1.docker",{"title":518,"path":519,"stem":520},"Kubernetes & Helm","\u002Fdeployment\u002Fkubernetes","6.deployment\u002F2.kubernetes",{"title":522,"path":523,"stem":524},"CI\u002FCD","\u002Fdeployment\u002Fci-cd","6.deployment\u002F3.ci-cd",{"title":526,"path":527,"stem":528},"CI\u002FCD Variables","\u002Fdeployment\u002Fvariables","6.deployment\u002F4.variables",{"title":530,"path":531,"stem":532},"Cloudflare","\u002Fdeployment\u002Fcloudflare","6.deployment\u002F5.cloudflare",{"title":534,"path":535,"stem":536},"Origin Protection","\u002Fdeployment\u002Forigin-protection","6.deployment\u002F5.origin-protection",{"title":538,"path":539,"stem":540},"Load Testing","\u002Fdeployment\u002Fload-testing","6.deployment\u002F6.load-testing",{"title":542,"path":543,"stem":544},"Performance Audit","\u002Fdeployment\u002Fperformance-audit","6.deployment\u002F6.performance-audit",{"title":546,"path":547,"stem":548},"Reading Lighthouse Scores","\u002Fdeployment\u002Freading-lighthouse-scores","6.deployment\u002F6.reading-lighthouse-scores",{"title":550,"path":551,"stem":552},"Browser Tests","\u002Fdeployment\u002Fbrowser-tests","6.deployment\u002F7.browser-tests",{"title":554,"path":555,"stem":556},"Faster Functional Tests","\u002Fdeployment\u002Ffaster-functional-tests","6.deployment\u002F7.faster-functional-tests",{"title":558,"path":559,"stem":560},"Troubleshooting","\u002Fdeployment\u002Ftroubleshooting","6.deployment\u002F7.troubleshooting",{"id":562,"title":550,"badge":563,"body":564,"description":1651,"extension":1652,"links":563,"meta":1653,"navigation":1131,"path":551,"seo":1654,"stem":552,"__hash__":1655},"docs\u002F6.deployment\u002F7.browser-tests.md",null,{"type":565,"value":566,"toc":1642},"minimark",[567,608,615,620,623,658,661,699,702,717,721,741,794,797,801,811,850,853,856,865,869,872,958,1028,1038,1064,1068,1079,1366,1438,1451,1456,1478,1512,1521,1571,1576,1623,1638],[568,569,570,571,575,576,583,584,583,591,598,599],"p",{},"The template has opt-in browser tests, in ",[572,573,574],"code",{},"e2e\u002F",". They take the php and app images this branch just built, start them in a throwaway stack, and use the site in Chromium the way visitors and editors do. When they're on, every deploy waits for them, so a build that breaks logging in or publishing never reaches a site (",[577,578,582],"a",{"href":579,"rel":580},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F157",[581],"nofollow","#157",", ",[577,585,588],{"href":586,"rel":587},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F3fd2c01237578dd0f009de10699402b257f5bf7c",[581],[572,589,590],{},"3fd2c01",[577,592,595],{"href":593,"rel":594},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8c36f8c89f9176495fc02f8f9480531c71eb4613",[581],[572,596,597],{},"8c36f8c","). ",[600,601,602,603],"em",{},"Since template 2.0.0-alpha.34 · ",[577,604,607],{"href":605,"rel":606},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.34",[581],"release notes",[568,609,610,611,614],{},"The template's ",[572,612,613],{},"e2e\u002FREADME.md"," is the short version of this page.",[616,617,619],"h2",{"id":618},"what-they-check","What they check",[568,621,622],{},"The template's tests cover:",[624,625,626,630,633,640,643,646,649,652,655],"ul",{},[627,628,629],"li",{},"every page in the sitemap renders and hydrates;",[627,631,632],{},"a public page is served from the cache on its second request;",[627,634,635,636,639],{},"a redirecting route answers with a ",[572,637,638],{},"308",";",[627,641,642],{},"the loading bar shows while a page loads after a client-side navigation;",[627,644,645],{},"a page's OG image renders;",[627,647,648],{},"an admin can log in and out;",[627,650,651],{},"an admin can edit and publish a text component, and an anonymous visitor then sees it;",[627,653,654],{},"an admin can upload and publish an image, and a visitor then sees it;",[627,656,657],{},"a password reset email links to a working reset page.",[568,659,660],{},"A run also fails when:",[624,662,663,677,680,692],{},[627,664,665,666,583,669,672,673,676],{},"an API write gets a ",[572,667,668],{},"405",[572,670,671],{},"406"," or ",[572,674,675],{},"415",", which means the front end and the API disagree about a method or a content type;",[627,678,679],{},"the page throws an uncaught error;",[627,681,682,683,687,688,691],{},"the API sent an ",[577,684,686],{"href":685},"\u002Fdeployment\u002Fkubernetes#error-mail","error mail",", meaning it answered a request with a ",[572,689,690],{},"5xx"," that the UI may have hidden;",[627,693,694,695,698],{},"not every listed test ran and passed (",[572,696,697],{},"check-all-ran.mjs","), so a skipped or filtered-out test can't pass a run.",[568,700,701],{},"Every timeout is bounded: 90 seconds a test, 30 seconds a navigation, 15 seconds an action and 10 seconds an assertion.",[703,704,706],"callout",{"icon":705},"i-heroicons-photo",[568,707,708,712,713,716],{},[709,710,711],"strong",{},"Image placeholder:"," the Playwright HTML report from a passing ",[572,714,715],{},"browser tests"," job, with the list of tests.",[616,718,720],{"id":719},"turning-them-on","Turning them on",[568,722,723,724,727,728,731,732,736,737,740],{},"Set the CI variable ",[572,725,726],{},"CWA_CI_E2E_ENABLED"," to ",[572,729,730],{},"\"true\""," (see ",[577,733,735],{"href":734},"\u002Fdeployment\u002Fvariables#pipeline-flags","CI\u002FCD Variables → Pipeline flags","). Nothing runs until you do. ",[572,738,739],{},"CWA_CI_TEST_DISABLED=\"true\""," skips them along with the other tests.",[624,742,743,772],{},[627,744,745,748,749,751,752,755,756,759,760,763,764,767,768,771],{},[709,746,747],{},"GitLab:"," a ",[572,750,715],{}," job in the ",[572,753,754],{},"test"," stage, after ",[572,757,758],{},"build api"," and ",[572,761,762],{},"build app",". The review, staging, canary and production jobs ",[572,765,766],{},"needs:"," it, so none of them runs when it fails. It runs Docker in Docker (",[572,769,770],{},"docker:27-dind",").",[627,773,774,777,778,781,782,785,786,789,790,793],{},[709,775,776],{},"GitHub:"," the reusable workflow ",[572,779,780],{},".github\u002Fworkflows\u002Fbrowser-tests.yml",". ",[572,783,784],{},"ci.yml"," calls it after the builds, before review and staging, and ",[572,787,788],{},"production.yml"," calls it on ",[572,791,792],{},"main","'s images before the deploy. A failed run stops those deploys. It has a 30-minute limit.",[568,795,796],{},"The Playwright report, with traces, screenshots and videos of failed tests, is kept as a job artifact for 14 days. GitLab also reads its JUnit file, so the results show in the pipeline's test report.",[616,798,800],{"id":799},"the-test-stack","The test stack",[568,802,803,806,807,810],{},[572,804,805],{},"e2e\u002Frun.sh"," starts ",[572,808,809],{},"e2e\u002Fcompose.yaml",", which has:",[624,812,813,827,834,840,843],{},[627,814,815,816,819,820,759,823,826],{},"the branch's php and app ",[709,817,818],{},"production"," images (",[572,821,822],{},"frankenphp_prod",[572,824,825],{},"prod",");",[627,828,829,830,833],{},"PostgreSQL on ",[572,831,832],{},"tmpfs",", so every run starts empty;",[627,835,836,837,639],{},"a Cloud Storage emulator for uploads, reached through php's ",[572,838,839],{},"GCLOUD_API_ENDPOINT",[627,841,842],{},"Mailpit, which catches every email, such as the password reset;",[627,844,845,846,849],{},"the Playwright runner, built from ",[572,847,848],{},"e2e\u002FDockerfile",".",[568,851,852],{},"It makes new secrets and a new JWT key pair for each run, waits for the API (migrations run on boot), loads the fixtures, flushes the HTTP cache and waits for the site. Then it runs the tests one at a time, and stops and deletes the stack.",[568,854,855],{},"The stack publishes no ports, so it can run beside your dev stack.",[568,857,858,860,861,864],{},[572,859,839],{}," is the only change the tests need in the API: ",[572,862,863],{},"GoogleCloudStorageFactory"," uses an emulator, with no credentials, when it's set. Leave it unset on every deploy, where empty means Google.",[616,866,868],{"id":867},"running-them-locally","Running them locally",[568,870,871],{},"You need Docker and openssl. Build the two production images, then run the tests against them:",[873,874,879],"pre",{"className":875,"code":876,"language":877,"meta":878,"style":878},"language-bash shiki shiki-themes github-light github-dark material-theme-palenight","docker build --target frankenphp_prod -t cwa-e2e-php:local api\ndocker build --target prod -t cwa-e2e-app:local app\nE2E_PHP_IMAGE=cwa-e2e-php:local E2E_APP_IMAGE=cwa-e2e-app:local sh e2e\u002Frun.sh\n","bash","",[572,880,881,910,930],{"__ignoreMap":878},[882,883,886,890,894,898,901,904,907],"span",{"class":884,"line":885},"line",1,[882,887,889],{"class":888},"sRCss","docker",[882,891,893],{"class":892},"sLL54"," build",[882,895,897],{"class":896},"szhYu"," --target",[882,899,900],{"class":892}," frankenphp_prod",[882,902,903],{"class":896}," -t",[882,905,906],{"class":892}," cwa-e2e-php:local",[882,908,909],{"class":892}," api\n",[882,911,913,915,917,919,922,924,927],{"class":884,"line":912},2,[882,914,889],{"class":888},[882,916,893],{"class":892},[882,918,897],{"class":896},[882,920,921],{"class":892}," prod",[882,923,903],{"class":896},[882,925,926],{"class":892}," cwa-e2e-app:local",[882,928,929],{"class":892}," app\n",[882,931,933,937,941,944,947,949,952,955],{"class":884,"line":932},3,[882,934,936],{"class":935},"sPB8G","E2E_PHP_IMAGE",[882,938,940],{"class":939},"sVlFx","=",[882,942,943],{"class":892},"cwa-e2e-php:local",[882,945,946],{"class":935}," E2E_APP_IMAGE",[882,948,940],{"class":939},[882,950,951],{"class":892},"cwa-e2e-app:local",[882,953,954],{"class":888}," sh",[882,956,957],{"class":892}," e2e\u002Frun.sh\n",[959,960,961,974],"table",{},[962,963,964],"thead",{},[965,966,967,971],"tr",{},[968,969,970],"th",{},"Variable",[968,972,973],{},"What it does",[975,976,977,994,1012],"tbody",{},[965,978,979,988],{},[980,981,982,984,985],"td",{},[572,983,936],{}," \u002F ",[572,986,987],{},"E2E_APP_IMAGE",[980,989,990,993],{},[709,991,992],{},"Required."," The php and app images to test. CI passes this branch's tags",[965,995,996,1001],{},[980,997,998],{},[572,999,1000],{},"E2E_ARGS",[980,1002,1003,1004,1007,1008,1011],{},"Runs part of the suite: a file, such as ",[572,1005,1006],{},"E2E_ARGS=\"tests\u002Fadmin.spec.ts\"",", or ",[572,1009,1010],{},"--grep …",". The every-test-ran check is skipped then",[965,1013,1014,1019],{},[980,1015,1016],{},[572,1017,1018],{},"E2E_KEEP_STACK",[980,1020,1021,1024,1025],{},[572,1022,1023],{},"true"," leaves the stack running afterwards and prints the admin password. Stop it with ",[572,1026,1027],{},"docker compose -f e2e\u002Fcompose.yaml down -v",[568,1029,1030,1031,759,1034,1037],{},"The reports land in ",[572,1032,1033],{},"e2e\u002Fplaywright-report\u002F",[572,1035,1036],{},"e2e\u002Ftest-results\u002F",". When a run fails, the script prints the last 80 lines of the php and app logs.",[703,1039,1041],{"icon":1040},"i-heroicons-information-circle",[568,1042,1043,1049,1050],{},[709,1044,1045,1046],{},"On an ARM Mac, set ",[572,1047,1048],{},"DOCKER_DEFAULT_PLATFORM=linux\u002Famd64"," to test images from your CI registry, which builds them for amd64 only. ",[600,1051,1052,1053,1055,1056,1063],{},"On ",[572,1054,792],{}," after template 2.0.0-alpha.35, not yet released (",[577,1057,1060],{"href":1058,"rel":1059},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F8949efe89eb2ac19319fbc1278227fbd9d3c2610",[581],[572,1061,1062],{},"8949efe",")",[616,1065,1067],{"id":1066},"writing-your-own-tests","Writing your own tests",[568,1069,1070,1071,1074,1075,1078],{},"Add your project's own flows under ",[572,1072,1073],{},"e2e\u002Ftests\u002F",", as ",[572,1076,1077],{},"*.spec.ts"," files.",[873,1080,1085],{"className":1081,"code":1082,"filename":1083,"language":1084,"meta":878,"style":878},"language-ts shiki shiki-themes github-light github-dark material-theme-palenight","import { expect, logIn, test } from '.\u002Ffixtures'\n\ntest('an admin sees the contact page, and so does a visitor', async ({ page, visitor }) => {\n  await logIn(page)\n  await page.goto('\u002Fcontact')\n  await expect(page.getByRole('button', { name: 'Edit' })).toBeVisible()\n\n  await visitor.goto('\u002Fcontact')\n  await expect(visitor.getByRole('heading', { name: 'Contact us', exact: true })).toBeVisible()\n})\n","e2e\u002Ftests\u002Fcontact.spec.ts","ts",[572,1086,1087,1127,1133,1176,1193,1216,1271,1276,1297,1358],{"__ignoreMap":878},[882,1088,1089,1093,1097,1100,1103,1106,1108,1111,1114,1117,1121,1124],{"class":884,"line":885},[882,1090,1092],{"class":1091},"sm4w6","import",[882,1094,1096],{"class":1095},"sOvfz"," {",[882,1098,1099],{"class":935}," expect",[882,1101,1102],{"class":1095},",",[882,1104,1105],{"class":935}," logIn",[882,1107,1102],{"class":1095},[882,1109,1110],{"class":935}," test",[882,1112,1113],{"class":1095}," }",[882,1115,1116],{"class":1091}," from",[882,1118,1120],{"class":1119},"seSrl"," '",[882,1122,1123],{"class":892},".\u002Ffixtures",[882,1125,1126],{"class":1119},"'\n",[882,1128,1129],{"class":884,"line":912},[882,1130,1132],{"emptyLinePlaceholder":1131},true,"\n",[882,1134,1135,1138,1141,1144,1147,1149,1151,1155,1158,1162,1164,1167,1170,1173],{"class":884,"line":932},[882,1136,754],{"class":1137},"sKpYG",[882,1139,1140],{"class":935},"(",[882,1142,1143],{"class":1119},"'",[882,1145,1146],{"class":892},"an admin sees the contact page, and so does a visitor",[882,1148,1143],{"class":1119},[882,1150,1102],{"class":1095},[882,1152,1154],{"class":1153},"swB56"," async",[882,1156,1157],{"class":1095}," ({",[882,1159,1161],{"class":1160},"sNTR6"," page",[882,1163,1102],{"class":1095},[882,1165,1166],{"class":1160}," visitor",[882,1168,1169],{"class":1095}," })",[882,1171,1172],{"class":1153}," =>",[882,1174,1175],{"class":1095}," {\n",[882,1177,1179,1182,1184,1187,1190],{"class":884,"line":1178},4,[882,1180,1181],{"class":1091},"  await",[882,1183,1105],{"class":1137},[882,1185,1140],{"class":1186},"sDHlG",[882,1188,1189],{"class":935},"page",[882,1191,1192],{"class":1186},")\n",[882,1194,1196,1198,1200,1202,1205,1207,1209,1212,1214],{"class":884,"line":1195},5,[882,1197,1181],{"class":1091},[882,1199,1161],{"class":935},[882,1201,849],{"class":1095},[882,1203,1204],{"class":1137},"goto",[882,1206,1140],{"class":1186},[882,1208,1143],{"class":1119},[882,1210,1211],{"class":892},"\u002Fcontact",[882,1213,1143],{"class":1119},[882,1215,1192],{"class":1186},[882,1217,1219,1221,1223,1225,1227,1229,1232,1234,1236,1239,1241,1243,1245,1248,1251,1253,1256,1258,1260,1263,1265,1268],{"class":884,"line":1218},6,[882,1220,1181],{"class":1091},[882,1222,1099],{"class":1137},[882,1224,1140],{"class":1186},[882,1226,1189],{"class":935},[882,1228,849],{"class":1095},[882,1230,1231],{"class":1137},"getByRole",[882,1233,1140],{"class":1186},[882,1235,1143],{"class":1119},[882,1237,1238],{"class":892},"button",[882,1240,1143],{"class":1119},[882,1242,1102],{"class":1095},[882,1244,1096],{"class":1095},[882,1246,1247],{"class":1186}," name",[882,1249,1250],{"class":1095},":",[882,1252,1120],{"class":1119},[882,1254,1255],{"class":892},"Edit",[882,1257,1143],{"class":1119},[882,1259,1113],{"class":1095},[882,1261,1262],{"class":1186},"))",[882,1264,849],{"class":1095},[882,1266,1267],{"class":1137},"toBeVisible",[882,1269,1270],{"class":1186},"()\n",[882,1272,1274],{"class":884,"line":1273},7,[882,1275,1132],{"emptyLinePlaceholder":1131},[882,1277,1279,1281,1283,1285,1287,1289,1291,1293,1295],{"class":884,"line":1278},8,[882,1280,1181],{"class":1091},[882,1282,1166],{"class":935},[882,1284,849],{"class":1095},[882,1286,1204],{"class":1137},[882,1288,1140],{"class":1186},[882,1290,1143],{"class":1119},[882,1292,1211],{"class":892},[882,1294,1143],{"class":1119},[882,1296,1192],{"class":1186},[882,1298,1300,1302,1304,1306,1309,1311,1313,1315,1317,1320,1322,1324,1326,1328,1330,1332,1335,1337,1339,1342,1344,1348,1350,1352,1354,1356],{"class":884,"line":1299},9,[882,1301,1181],{"class":1091},[882,1303,1099],{"class":1137},[882,1305,1140],{"class":1186},[882,1307,1308],{"class":935},"visitor",[882,1310,849],{"class":1095},[882,1312,1231],{"class":1137},[882,1314,1140],{"class":1186},[882,1316,1143],{"class":1119},[882,1318,1319],{"class":892},"heading",[882,1321,1143],{"class":1119},[882,1323,1102],{"class":1095},[882,1325,1096],{"class":1095},[882,1327,1247],{"class":1186},[882,1329,1250],{"class":1095},[882,1331,1120],{"class":1119},[882,1333,1334],{"class":892},"Contact us",[882,1336,1143],{"class":1119},[882,1338,1102],{"class":1095},[882,1340,1341],{"class":1186}," exact",[882,1343,1250],{"class":1095},[882,1345,1347],{"class":1346},"swWMF"," true",[882,1349,1113],{"class":1095},[882,1351,1262],{"class":1186},[882,1353,849],{"class":1095},[882,1355,1267],{"class":1137},[882,1357,1270],{"class":1186},[882,1359,1361,1364],{"class":884,"line":1360},10,[882,1362,1363],{"class":1095},"}",[882,1365,1192],{"class":935},[624,1367,1368,1393,1402,1428],{},[627,1369,1370,1382,1383,1386,1387,1389,1390,1392],{},[709,1371,1372,1373,759,1375,1378,1379],{},"Import ",[572,1374,754],{},[572,1376,1377],{},"expect"," from ",[572,1380,1381],{},"tests\u002Ffixtures.ts",", never from ",[572,1384,1385],{},"@playwright\u002Ftest",". Its ",[572,1388,1189],{}," fixture is what fails a test on a refused API write or an uncaught error. Importing types from ",[572,1391,1385],{}," is fine.",[627,1394,1395,1401],{},[709,1396,1397,1400],{},[572,1398,1399],{},"logIn(page)"," signs in as the admin."," The password is made for each run and loaded with the fixtures.",[627,1403,1404,1410,1411,1413,1414,1416,1417],{},[709,1405,1406,1407,1409],{},"Use the ",[572,1408,1308],{}," fixture to see a page as an anonymous visitor",", beside the signed-in ",[572,1412,1189],{},". It has its own browser context, is checked the same way as ",[572,1415,1189],{},", and is closed after the test. ",[600,1418,1052,1419,1055,1421,1063],{},[572,1420,792],{},[577,1422,1425],{"href":1423,"rel":1424},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F43ece66d7a6a2c6e478904d45b2312a248fd634b",[581],[572,1426,1427],{},"43ece66",[627,1429,1430,1433,1434,1437],{},[709,1431,1432],{},"Tests share one database and run one at a time."," A test that edits content should use its own unique text, such as one with ",[572,1435,1436],{},"Date.now()"," in it, so it can't match another test's.",[568,1439,1440,1441,1444,1445,1450],{},"The template's editing test publishes straight after the last keystroke. Since ",[572,1442,1443],{},"@cwa\u002Fnuxt"," 2.0.0-alpha.16, Publish sends the waiting save first (",[577,1446,1449],{"href":1447,"rel":1448},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcwa-nuxt-module\u002Fissues\u002F394",[581],"cwa-nuxt-module#394","), so the test checks that this still works.",[1452,1453,1455],"h3",{"id":1454},"type-checks-lint-and-conventions","Type checks, lint and conventions",[568,1457,1458,1459,1461,1462],{},"The tests are type-checked and linted before they run, and every rule is an error, so a type or lint error fails the ",[572,1460,715],{}," job. Playwright only strips types, so nothing else would catch them. ",[600,1463,1052,1464,1055,1466,583,1471,1063],{},[572,1465,792],{},[577,1467,1469],{"href":1423,"rel":1468},[581],[572,1470,1427],{},[577,1472,1475],{"href":1473,"rel":1474},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F08b40db59ffd6f41fbdb3c716d5190bbbcd0a871",[581],[572,1476,1477],{},"08b40db",[624,1479,1480,1489],{},[627,1481,1482,1485,1486,639],{},[572,1483,1484],{},"tsc",", with the strict ",[572,1487,1488],{},"e2e\u002Ftsconfig.json",[627,1490,1491,1492,1495,1496,1499,1500,1503,1504,1507,1508,1511],{},"ESLint, with ",[572,1493,1494],{},"e2e\u002Feslint.config.mjs",": ",[572,1497,1498],{},"@nuxt\u002Feslint-config","'s stylistic rules (the module's style), ",[572,1501,1502],{},"eslint-plugin-playwright",", and typescript-eslint's ",[572,1505,1506],{},"no-floating-promises",", which also catches a missing ",[572,1509,1510],{},"await"," on your own helpers.",[568,1513,1514,1515,781,1517,1520],{},"Run them yourself in ",[572,1516,574],{},[572,1518,1519],{},"npm ci"," also gives your editor the types:",[873,1522,1524],{"className":875,"code":1523,"language":877,"meta":878,"style":878},"cd e2e\nnpm ci\nnpm run typecheck\nnpm run lint\nnpm run lint:fix\n",[572,1525,1526,1535,1543,1553,1562],{"__ignoreMap":878},[882,1527,1528,1532],{"class":884,"line":885},[882,1529,1531],{"class":1530},"s3Ny6","cd",[882,1533,1534],{"class":892}," e2e\n",[882,1536,1537,1540],{"class":884,"line":912},[882,1538,1539],{"class":888},"npm",[882,1541,1542],{"class":892}," ci\n",[882,1544,1545,1547,1550],{"class":884,"line":932},[882,1546,1539],{"class":888},[882,1548,1549],{"class":892}," run",[882,1551,1552],{"class":892}," typecheck\n",[882,1554,1555,1557,1559],{"class":884,"line":1178},[882,1556,1539],{"class":888},[882,1558,1549],{"class":892},[882,1560,1561],{"class":892}," lint\n",[882,1563,1564,1566,1568],{"class":884,"line":1195},[882,1565,1539],{"class":888},[882,1567,1549],{"class":892},[882,1569,1570],{"class":892}," lint:fix\n",[568,1572,1573,1574,1250],{},"The rules come with these conventions, from the template's ",[572,1575,613],{},[624,1577,1578,1584,1596,1609],{},[627,1579,1580,1583],{},[709,1581,1582],{},"Await every Playwright call and every helper"," that returns a promise.",[627,1585,1586,1495,1589,1592,1593,849],{},[709,1587,1588],{},"Use retrying assertions",[572,1590,1591],{},"await expect(locator).toBeVisible()",", not ",[572,1594,1595],{},"expect(await locator.isVisible())",[627,1597,1598,1601,1602,759,1605,1608],{},[709,1599,1600],{},"No fixed waits and no forced clicks."," ",[572,1603,1604],{},"waitForTimeout",[572,1606,1607],{},"force: true"," are errors. Wait for the response, URL or element the test is about.",[627,1610,1611,1495,1614,583,1616,672,1619,1622],{},[709,1612,1613],{},"Find elements the way a person does",[572,1615,1231],{},[572,1617,1618],{},"getByLabel",[572,1620,1621],{},"getByText",", with exact names. Use a CSS selector only where the markup gives nothing better, with a comment that says why.",[703,1624,1626],{"icon":1625},"i-heroicons-light-bulb",[568,1627,1628,1629,759,1631,1633,1634,1637],{},"Keep the Playwright image in ",[572,1630,848],{},[572,1632,1385],{}," in ",[572,1635,1636],{},"e2e\u002Fpackage.json"," on the same version.",[1639,1640,1641],"style",{},"html pre.shiki code .sRCss, html code.shiki .sRCss{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#FFCB6B}html pre.shiki code .sLL54, html code.shiki .sLL54{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#C3E88D}html pre.shiki code .szhYu, html code.shiki .szhYu{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#C3E88D}html pre.shiki code .sPB8G, html code.shiki .sPB8G{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#BABED8}html pre.shiki code .sVlFx, html code.shiki .sVlFx{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sm4w6, html code.shiki .sm4w6{--shiki-light:#D73A49;--shiki-light-font-style:inherit;--shiki-default:#F97583;--shiki-default-font-style:inherit;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sOvfz, html code.shiki .sOvfz{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#89DDFF}html pre.shiki code .seSrl, html code.shiki .seSrl{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#89DDFF}html pre.shiki code .sKpYG, html code.shiki .sKpYG{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#82AAFF}html pre.shiki code .swB56, html code.shiki .swB56{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#C792EA}html pre.shiki code .sNTR6, html code.shiki .sNTR6{--shiki-light:#E36209;--shiki-light-font-style:inherit;--shiki-default:#FFAB70;--shiki-default-font-style:inherit;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sDHlG, html code.shiki .sDHlG{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#F07178}html pre.shiki code .swWMF, html code.shiki .swWMF{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#FF9CAC}html pre.shiki code .s3Ny6, html code.shiki .s3Ny6{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#82AAFF}",{"title":878,"searchDepth":912,"depth":912,"links":1643},[1644,1645,1646,1647,1648],{"id":618,"depth":912,"text":619},{"id":719,"depth":912,"text":720},{"id":799,"depth":912,"text":800},{"id":867,"depth":912,"text":868},{"id":1066,"depth":912,"text":1067,"children":1649},[1650],{"id":1454,"depth":932,"text":1455},"The template's opt-in Playwright tests run each branch's production images in a throwaway stack and drive the site in Chromium. They gate every deploy. Run them locally and add your own.","md",{},{"title":550,"description":1651},"1JZvid1gHjlBmoTgD0wVOR5r_0eYRNjbb5m9ZS-8_Wk",[1657,1659],{"title":546,"path":547,"stem":548,"description":1658,"children":-1},"Why a fast CWA page can score anywhere from 60 to 96 on mobile Lighthouse, how to read the result, and what a project should check first — third-party scripts, icons, images and its own front-end bundle.",{"title":554,"path":555,"stem":556,"description":1660,"children":-1},"When a project's functional test suite slows CI down — measure where the time goes, reset the database without rebuilding the schema, and shard the job by test.",1792148294140]