@screenly/edge-apps 1.5.0 → 26.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.
package/README.md CHANGED
@@ -342,6 +342,43 @@ afterEach(() => {
342
342
  })
343
343
  ```
344
344
 
345
+ ### Screenshot Testing
346
+
347
+ `captureScreenshot()` consolidates the Playwright boilerplate (browser
348
+ context/page setup, clock and `screenly.js` mocking, navigation, and cleanup)
349
+ that's otherwise duplicated across every resolution in an Edge App's e2e
350
+ screenshot spec. Keep `test()` in your spec file so Playwright still reports
351
+ the correct source location; pass app-specific route mocks through the
352
+ optional `setupMocks` callback, omitting it when there's nothing app-specific
353
+ to mock.
354
+
355
+ ```typescript
356
+ import { test } from '@playwright/test'
357
+ import {
358
+ captureScreenshot,
359
+ createMockScreenlyForScreenshots,
360
+ RESOLUTIONS,
361
+ } from '@screenly/edge-apps/test/screenshots'
362
+
363
+ const { screenlyJsContent } = createMockScreenlyForScreenshots()
364
+
365
+ for (const { width, height } of RESOLUTIONS) {
366
+ test(`screenshot ${width}x${height}`, async ({ browser }) => {
367
+ await captureScreenshot(browser, {
368
+ width,
369
+ height,
370
+ filenamePrefix: 'my-edge-app',
371
+ screenlyJsContent,
372
+ setupMocks: async (page) => {
373
+ await page.route('**/api**', async (route) => {
374
+ await route.fulfill({ status: 200, body: '{}' })
375
+ })
376
+ },
377
+ })
378
+ })
379
+ }
380
+ ```
381
+
345
382
  ## Types
346
383
 
347
384
  ```typescript
@@ -18,6 +18,37 @@ interface PlaywrightRouteFulfillOptions {
18
18
  interface PlaywrightRoute {
19
19
  fulfill(options: PlaywrightRouteFulfillOptions): Promise<void>;
20
20
  }
21
+ interface PlaywrightRequest {
22
+ url(): string;
23
+ method(): string;
24
+ }
25
+ type PlaywrightRouteHandler = (route: PlaywrightRoute, request: PlaywrightRequest) => Promise<void> | void;
26
+ interface PlaywrightRoutable {
27
+ route(url: string | RegExp, handler: PlaywrightRouteHandler): Promise<void>;
28
+ }
29
+ interface PlaywrightPage extends PlaywrightRoutable {
30
+ clock: {
31
+ setFixedTime(time: Date | number | string): Promise<void>;
32
+ };
33
+ goto(url: string): Promise<unknown>;
34
+ waitForLoadState(state: 'networkidle'): Promise<void>;
35
+ screenshot(options: {
36
+ path: string;
37
+ fullPage: boolean;
38
+ }): Promise<Buffer>;
39
+ }
40
+ interface PlaywrightBrowserContext {
41
+ newPage(): Promise<PlaywrightPage>;
42
+ close(): Promise<void>;
43
+ }
44
+ interface PlaywrightBrowser {
45
+ newContext(options: {
46
+ viewport: {
47
+ width: number;
48
+ height: number;
49
+ };
50
+ }): Promise<PlaywrightBrowserContext>;
51
+ }
21
52
  /**
22
53
  * Standard resolutions for screenshot testing
23
54
  * Covers all supported Screenly player resolutions
@@ -92,17 +123,13 @@ export interface OpenWeatherMocks {
92
123
  * @param page - Playwright page object
93
124
  * @param mocks - Mock data for OpenWeather API endpoints
94
125
  */
95
- export declare function setupOpenWeatherMocks(page: {
96
- route: (url: string, handler: (route: PlaywrightRoute) => Promise<void>) => Promise<void>;
97
- }, mocks: OpenWeatherMocks): Promise<void>;
126
+ export declare function setupOpenWeatherMocks(page: PlaywrightRoutable, mocks: OpenWeatherMocks): Promise<void>;
98
127
  /**
99
128
  * Setup screenly.js route mock in Playwright
100
129
  * @param page - Playwright page object
101
130
  * @param screenlyJsContent - JavaScript content string for screenly.js
102
131
  */
103
- export declare function setupScreenlyJsMock(page: {
104
- route: (url: string, handler: (route: PlaywrightRoute) => Promise<void>) => Promise<void>;
105
- }, screenlyJsContent: string): Promise<void>;
132
+ export declare function setupScreenlyJsMock(page: PlaywrightRoutable, screenlyJsContent: string): Promise<void>;
106
133
  /**
107
134
  * Freeze the browser clock to a fixed point in time before page load.
108
135
  * Must be called before `page.goto()` to ensure the clock is set before
@@ -117,4 +144,32 @@ export declare function setupClockMock(page: {
117
144
  setFixedTime(time: Date | number | string): Promise<void>;
118
145
  };
119
146
  }, date?: Date | number | string): Promise<void>;
147
+ /**
148
+ * Options for {@link captureScreenshot}.
149
+ */
150
+ export interface CaptureScreenshotOptions {
151
+ /** Viewport width */
152
+ width: number;
153
+ /** Viewport height */
154
+ height: number;
155
+ /** Prefix used for the output screenshot filename */
156
+ filenamePrefix: string;
157
+ /** JavaScript content string for screenly.js mocking */
158
+ screenlyJsContent: string;
159
+ /** Callback for app-specific route mocks, called before `page.goto()` */
160
+ setupMocks?: (page: PlaywrightPage) => Promise<void> | void;
161
+ }
162
+ /**
163
+ * Captures a single screenshot at the given viewport size, handling the
164
+ * common boilerplate shared across Edge App screenshot specs: browser
165
+ * context/page setup, clock and screenly.js mocking, navigation, and
166
+ * cleanup. App-specific route mocks are supplied via `setupMocks`.
167
+ *
168
+ * Callers should keep `test()` in their spec files (rather than wrapping it
169
+ * in this helper) so Playwright reports the correct source location.
170
+ *
171
+ * @param browser - Playwright browser object
172
+ * @param options - Capture options
173
+ */
174
+ export declare function captureScreenshot(browser: PlaywrightBrowser, { width, height, filenamePrefix, screenlyJsContent, setupMocks, }: CaptureScreenshotOptions): Promise<void>;
120
175
  //# sourceMappingURL=screenshots.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../../src/test/screenshots.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EACjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAE9C,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAE7B;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,MAAmC,CAAA;AAErE,UAAU,6BAA6B;IACrC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,UAAU,eAAe;IACvB,OAAO,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/D;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWd,CAAA;AAEV;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAI1C;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,cAAc,GAC7B,MAAM,CAKR;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,GAAE,OAAO,CAAC,gBAAgB,CAAM,EACxC,QAAQ,GAAE,OAAO,CAAC,gBAAgB,CAAM,EACxC,YAAY,SAA0B,GACrC;IAAE,YAAY,EAAE,cAAc,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAS7D;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE;IACJ,KAAK,EAAE,CACL,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,KAC/C,OAAO,CAAC,IAAI,CAAC,CAAA;CACnB,EACD,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,IAAI,CAAC,CAuCf;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE;IACJ,KAAK,EAAE,CACL,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,KAC/C,OAAO,CAAC,IAAI,CAAC,CAAA;CACnB,EACD,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE;IACJ,KAAK,EAAE;QAAE,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAA;CACrE,EACD,IAAI,GAAE,IAAI,GAAG,MAAM,GAAG,MAA8B,GACnD,OAAO,CAAC,IAAI,CAAC,CAEf"}
1
+ {"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../../src/test/screenshots.ts"],"names":[],"mappings":"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"}
@@ -125,3 +125,34 @@ export async function setupScreenlyJsMock(page, screenlyJsContent) {
125
125
  export async function setupClockMock(page, date = FIXED_SCREENSHOT_DATE) {
126
126
  await page.clock.setFixedTime(date);
127
127
  }
128
+ /**
129
+ * Captures a single screenshot at the given viewport size, handling the
130
+ * common boilerplate shared across Edge App screenshot specs: browser
131
+ * context/page setup, clock and screenly.js mocking, navigation, and
132
+ * cleanup. App-specific route mocks are supplied via `setupMocks`.
133
+ *
134
+ * Callers should keep `test()` in their spec files (rather than wrapping it
135
+ * in this helper) so Playwright reports the correct source location.
136
+ *
137
+ * @param browser - Playwright browser object
138
+ * @param options - Capture options
139
+ */
140
+ export async function captureScreenshot(browser, { width, height, filenamePrefix, screenlyJsContent, setupMocks = async () => { }, }) {
141
+ const screenshotsDir = getScreenshotsDir();
142
+ const context = await browser.newContext({ viewport: { width, height } });
143
+ try {
144
+ const page = await context.newPage();
145
+ await setupClockMock(page);
146
+ await setupScreenlyJsMock(page, screenlyJsContent);
147
+ await setupMocks(page);
148
+ await page.goto('/');
149
+ await page.waitForLoadState('networkidle');
150
+ await page.screenshot({
151
+ path: path.join(screenshotsDir, `${filenamePrefix}-${width}x${height}.png`),
152
+ fullPage: false,
153
+ });
154
+ }
155
+ finally {
156
+ await context.close();
157
+ }
158
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@screenly/edge-apps",
3
- "version": "1.5.0",
3
+ "version": "26.8.0",
4
4
  "description": "A TypeScript library for interfacing with Screenly Edge Apps API",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,33 +1,19 @@
1
1
  import { test } from '@playwright/test'
2
2
  import {
3
+ captureScreenshot,
3
4
  createMockScreenlyForScreenshots,
4
- getScreenshotsDir,
5
5
  RESOLUTIONS,
6
- setupClockMock,
7
- setupScreenlyJsMock,
8
6
  } from '@screenly/edge-apps/test/screenshots'
9
- import path from 'path'
10
7
 
11
8
  const { screenlyJsContent } = createMockScreenlyForScreenshots()
12
9
 
13
10
  for (const { width, height } of RESOLUTIONS) {
14
11
  test(`screenshot ${width}x${height}`, async ({ browser }) => {
15
- const screenshotsDir = getScreenshotsDir()
16
-
17
- const context = await browser.newContext({ viewport: { width, height } })
18
- const page = await context.newPage()
19
-
20
- await setupClockMock(page)
21
- await setupScreenlyJsMock(page, screenlyJsContent)
22
-
23
- await page.goto('/')
24
- await page.waitForLoadState('networkidle')
25
-
26
- await page.screenshot({
27
- path: path.join(screenshotsDir, `${width}x${height}.png`),
28
- fullPage: false,
12
+ await captureScreenshot(browser, {
13
+ width,
14
+ height,
15
+ filenamePrefix: '{{APP_NAME}}',
16
+ screenlyJsContent,
29
17
  })
30
-
31
- await context.close()
32
18
  })
33
19
  }