@@ -53,6 +53,31 @@ test.describe("SEO Smoke Tests", () => {
5353 ) ;
5454 } ) ;
5555
56+ test ( "AI course builder stays usable on desktop and mobile" , async ( { page } ) => {
57+ const runtimeErrors : string [ ] = [ ] ;
58+ page . on ( "console" , ( message ) => {
59+ if ( message . type ( ) === "error" ) runtimeErrors . push ( message . text ( ) ) ;
60+ } ) ;
61+ page . on ( "pageerror" , ( error ) => runtimeErrors . push ( error . message ) ) ;
62+
63+ for ( const viewport of [
64+ { width : 1280 , height : 900 } ,
65+ { width : 390 , height : 844 } ,
66+ ] ) {
67+ await page . setViewportSize ( viewport ) ;
68+ await page . goto ( "/ai-course-builder" ) ;
69+ await expect ( page . getByRole ( "heading" , { level : 1 } ) ) . toBeVisible ( ) ;
70+ await expect ( page . getByRole ( "link" , { name : "Build your first course" } ) ) . toBeVisible ( ) ;
71+ expect (
72+ await page . evaluate (
73+ ( ) => document . documentElement . scrollWidth <= document . documentElement . clientWidth ,
74+ ) ,
75+ ) . toBe ( true ) ;
76+ }
77+
78+ expect ( runtimeErrors ) . toEqual ( [ ] ) ;
79+ } ) ;
80+
5681 test ( "docs pages have meta tags" , async ( { page } ) => {
5782 await page . goto ( "/docs/cli" ) ;
5883 const title = await page . title ( ) ;
@@ -65,14 +90,22 @@ test.describe("SEO Smoke Tests", () => {
6590 test ( "homepage has JSON-LD structured data" , async ( { page } ) => {
6691 await page . goto ( "/" ) ;
6792 const jsonLd = await page . locator ( 'script[type="application/ld+json"]' ) . allTextContents ( ) ;
68- // JSON-LD may not be present on all brand configs — skip if absent
93+ // JSON-LD can vary across brand configurations.
6994 if ( jsonLd . length === 0 ) {
7095 test . skip ( true , "No JSON-LD on this brand's homepage" ) ;
7196 return ;
7297 }
7398 // Verify at least one schema parses as valid JSON
7499 const data = JSON . parse ( jsonLd [ 0 ] ) ;
75100 expect ( data [ "@context" ] ) . toBe ( "https://schema.org" ) ;
101+
102+ const schemas = jsonLd . map ( ( value ) => JSON . parse ( value ) ) ;
103+ const schemaTypes = schemas . map ( ( schema ) => schema [ "@type" ] ) ;
104+ expect ( schemaTypes . filter ( ( type ) => type === "SoftwareApplication" ) ) . toHaveLength ( 1 ) ;
105+ expect ( schemaTypes ) . not . toContain ( "Course" ) ;
106+ expect ( schemaTypes ) . not . toContain ( "EducationalOccupationalCredential" ) ;
107+ const website = schemas . find ( ( schema ) => schema [ "@type" ] === "WebSite" ) ;
108+ expect ( website ?. potentialAction ) . toBeUndefined ( ) ;
76109 } ) ;
77110
78111 test ( "sitemap.xml returns valid XML" , async ( { request } ) => {
@@ -93,16 +126,22 @@ test.describe("SEO Smoke Tests", () => {
93126 expect ( body ) . toContain ( "Allow: /" ) ;
94127 // Crawlers need access to private pages to read their noindex metadata.
95128 expect ( body ) . not . toContain ( "Disallow:" ) ;
129+ for ( const userAgent of [
130+ "OAI-SearchBot" ,
131+ "ClaudeBot" ,
132+ "Claude-User" ,
133+ "Claude-SearchBot" ,
134+ "PerplexityBot" ,
135+ "Perplexity-User" ,
136+ ] ) {
137+ expect ( body ) . toContain ( `User-Agent: ${ userAgent } ` ) ;
138+ }
96139 } ) ;
97140
98141 test ( "llms.txt is accessible" , async ( { request } ) => {
99142 const res = await request . get ( "/llms.txt" ) ;
100- // llms.txt may 500 in dev if brand resolution fails — skip in that case
101- if ( res . status ( ) === 500 ) {
102- test . skip ( true , "llms.txt route errors in dev (brand resolution)" ) ;
103- return ;
104- }
105143 expect ( res . status ( ) ) . toBe ( 200 ) ;
144+ expect ( res . headers ( ) [ "content-type" ] ) . toContain ( "text/markdown" ) ;
106145 const body = await res . text ( ) ;
107146 expect ( body . toLowerCase ( ) ) . toContain ( "graspful" ) ;
108147 } ) ;
0 commit comments