@@ -9,9 +9,11 @@ import Koa from 'koa';
99import type { Browser , Page } from '@playwright/test' ;
1010import type { Server } from 'node:http' ;
1111import type { AddressInfo } from 'node:net' ;
12+ import type { LitElement } from 'lit' ;
1213
13- interface SSRDemoConfig {
14- demoDir : URL ;
14+ interface SSRPageConfig {
15+ demoDir ?: URL ;
16+ demoContent ?: string ;
1517 importSpecifiers : string [ ] ;
1618 tagName : string ;
1719 browser : Browser ;
@@ -25,44 +27,64 @@ export class SSRPage {
2527 private app : Koa ;
2628 private server ! : Server ;
2729 private host ! : string ;
28- private page ! : Page ;
2930 private demoPaths ! : string [ ] ;
3031
31- constructor (
32- private config : SSRDemoConfig ,
33- ) {
32+ public page ! : Page ;
33+
34+ constructor ( private config : SSRPageConfig ) {
3435 this . app = new Koa ( ) ;
35- this . app . use ( async ( ctx , next ) => {
36+ this . app . use ( this . middleware ( config ) ) ;
37+ }
38+
39+ private middleware ( { demoContent, demoDir, importSpecifiers } : SSRPageConfig ) {
40+ return async ( ctx : Koa . Context , next : Koa . Next ) => {
3641 if ( ctx . method === 'GET' ) {
37- const origPath = ctx . request . path . replace ( / ^ \/ / , '' ) ;
38- const demoDir = config . demoDir . href ;
39- const fileUrl = resolve ( demoDir , origPath ) ;
40- if ( ctx . request . path . endsWith ( '.html' ) ) {
42+ if ( demoContent ) {
4143 try {
42- const content = await readFile ( fileURLToPath ( fileUrl ) , 'utf-8' ) ;
43- ctx . response . body = await renderGlobal ( content , this . config . importSpecifiers ) ;
44+ ctx . response . body = await renderGlobal (
45+ demoContent ,
46+ importSpecifiers ,
47+ ) ;
4448 } catch ( e ) {
4549 ctx . response . status = 500 ;
4650 ctx . response . body = ( e as Error ) . stack ;
4751 }
48- } else {
49- try {
50- ctx . response . body = await readFile ( fileURLToPath ( fileUrl ) ) ;
51- } catch ( e ) {
52- ctx . throw ( 500 , e as Error ) ;
52+ } else if ( demoDir ) {
53+ const origPath = ctx . request . path . replace ( / ^ \/ / , '' ) ;
54+ const { href } = demoDir ;
55+ const fileUrl = resolve ( href , origPath ) ;
56+ if ( ctx . request . path . endsWith ( '.html' ) ) {
57+ try {
58+ const content = await readFile ( fileURLToPath ( fileUrl ) , 'utf-8' ) ;
59+ ctx . response . body = await renderGlobal ( content , importSpecifiers ) ;
60+ } catch ( e ) {
61+ ctx . response . status = 500 ;
62+ ctx . response . body = ( e as Error ) . stack ;
63+ }
64+ } else {
65+ try {
66+ ctx . response . body = await readFile ( fileURLToPath ( fileUrl ) ) ;
67+ } catch ( e ) {
68+ ctx . throw ( 500 , e as Error ) ;
69+ }
5370 }
71+ } else {
72+ throw new Error ( 'SSRPage must either have a demoDir URL or a demoContent string' ) ;
5473 }
5574 } else {
5675 return next ( ) ;
5776 }
58- } ) ;
77+ } ;
5978 }
6079
6180 private async initPage ( ) {
6281 this . page ??= await ( await this . config . browser . newContext ( {
6382 javaScriptEnabled : false ,
6483 } ) )
6584 . newPage ( ) ;
85+ if ( this . config . demoContent ) {
86+ await this . page . goto ( `${ this . host } test.html` ) ;
87+ }
6688 }
6789
6890 private async initServer ( ) {
@@ -72,7 +94,7 @@ export class SSRPage {
7294 }
7395 const { address = 'localhost' , port = 0 } = this . server . address ( ) as AddressInfo ;
7496 this . host ??= `http://${ address . replace ( '::' , 'localhost' ) } :${ port } /` ;
75- this . demoPaths ??= ( await readdir ( this . config . demoDir ) )
97+ this . demoPaths ??= ! this . config . demoDir ? [ ] : ( await readdir ( this . config . demoDir ) )
7698 . filter ( x => x . endsWith ( '.html' ) )
7799 . map ( x => new URL ( x , this . host ) . href ) ;
78100 }
@@ -82,6 +104,22 @@ export class SSRPage {
82104 ! this . server ? rej ( 'no server' ) : this . server ?. close ( e => e ? rej ( e ) : res ( ) ) ) ;
83105 }
84106
107+ /**
108+ * Take a visual regression snapshot and save it to disk
109+ * @param url url to the demo file
110+ */
111+ private async snapshot ( url : string ) {
112+ const response = await this . page . goto ( url , { waitUntil : 'load' } ) ;
113+ if ( response ?. status ( ) === 404 ) {
114+ throw new Error ( `Not Found: ${ url } ` ) ;
115+ }
116+ expect ( response ?. status ( ) , await response ?. text ( ) )
117+ . toEqual ( 200 ) ;
118+ const snapshot = await this . page . screenshot ( { fullPage : true } ) ;
119+ expect ( snapshot , new URL ( url ) . pathname )
120+ . toMatchSnapshot ( `${ this . config . tagName } -${ basename ( url ) } .png` ) ;
121+ }
122+
85123 /**
86124 * Creates visual regression snapshots for each demo in the server's `demoDir`
87125 */
@@ -99,19 +137,9 @@ export class SSRPage {
99137 }
100138 }
101139
102- /**
103- * Take a visual regression snapshot and save it to disk
104- * @param url url to the demo file
105- */
106- private async snapshot ( url : string ) {
107- const response = await this . page . goto ( url , { waitUntil : 'load' } ) ;
108- if ( response ?. status ( ) === 404 ) {
109- throw new Error ( `Not Found: ${ url } ` ) ;
110- }
111- expect ( response ?. status ( ) , await response ?. text ( ) )
112- . toEqual ( 200 ) ;
113- const snapshot = await this . page . screenshot ( { fullPage : true } ) ;
114- expect ( snapshot , new URL ( url ) . pathname )
115- . toMatchSnapshot ( `${ this . config . tagName } -${ basename ( url ) } .png` ) ;
140+ async updateCompleteFor ( tagName : string ) : Promise < void > {
141+ await this . initServer ( ) ;
142+ await this . initPage ( ) ;
143+ await this . page . $eval ( tagName , el => ( el as LitElement ) . updateComplete ) ;
116144 }
117145}
0 commit comments