Skip to content

Commit 80fdf3d

Browse files
committed
✨ Align web SDK plugin contracts
1 parent 768b2fe commit 80fdf3d

45 files changed

Lines changed: 1344 additions & 377 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

clients/static-site/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
121121
### Changed
122122
- **Breaking: Screenshot naming format updated** - Screenshot names no longer include viewport suffixes (e.g., `@mobile`, `@desktop`). Instead, viewport information is now stored as properties for better organization and compatibility with file system restrictions.
123123
- **Before:** `blog/post-1@mobile` (could cause validation errors with slashes)
124-
- **After:** Name: `blog-post-1`, Properties: `{ viewport: 'mobile', viewportWidth: 375, viewportHeight: 667 }`
124+
- **After:** Name: `blog-post-1`, Properties: `{ viewport: 'mobile', viewport_width: 375, viewport_height: 667 }`
125125
- Path separators (`/` and `\`) are now replaced with hyphens for cleaner, more portable names
126126
- This change improves compatibility with Vizzly's security validation and enables better grouping in the dashboard
127127

clients/static-site/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Stubborn Mule Software
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

clients/static-site/README.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { run } from '@vizzly-testing/static-site';
3838

3939
await run('./dist', {
4040
viewports: 'mobile:375x667,desktop:1920x1080',
41-
concurrency: 3,
41+
concurrency: 4,
4242
}, {
4343
output: console,
4444
config: vizzlyConfig,
@@ -58,6 +58,10 @@ export default {
5858
// Standard Vizzly config
5959
server: { port: 47392 },
6060
build: { environment: 'test' },
61+
comparison: {
62+
threshold: 0.1,
63+
minClusterSize: 2,
64+
},
6165

6266
// Static Site plugin config
6367
staticSite: {
@@ -68,13 +72,16 @@ export default {
6872
],
6973

7074
browser: {
75+
type: 'chromium',
7176
headless: true,
7277
args: ['--no-sandbox'],
7378
},
7479

7580
screenshot: {
7681
fullPage: true,
7782
omitBackground: false,
83+
timeout: 45000,
84+
requestTimeout: 60000,
7885
},
7986

8087
// Concurrency auto-detected from CPU cores (min 2, max 8)
@@ -94,6 +101,10 @@ export default {
94101
};
95102
```
96103

104+
The shared top-level `comparison` section controls cloud/run thresholds. The
105+
`staticSite` section stays focused on page discovery, browser settings,
106+
viewports, and screenshot capture.
107+
97108
### Interactions File (Optional)
98109

99110
For page-specific interactions and overrides, create a `vizzly.static-site.js` file:
@@ -117,6 +128,7 @@ export default {
117128
viewports: ['mobile', 'desktop'],
118129
},
119130
'/pricing': {
131+
interaction: 'products/*',
120132
screenshot: { fullPage: true },
121133
},
122134
},
@@ -136,15 +148,18 @@ Configuration is merged in this order (later overrides earlier):
136148

137149
- `--viewports <list>` - Comma-separated viewport definitions (format: `name:WxH`)
138150
- `--concurrency <n>` - Number of parallel browser tabs (default: auto-detected based on CPU cores, min 2, max 8)
151+
- `--browser <type>` - Browser engine to use: `chromium`, `firefox`, or `webkit`
139152
- `--include <pattern>` - Include page pattern (glob)
140153
- `--exclude <pattern>` - Exclude page pattern (glob)
141-
- `--browser-args <args>` - Additional Puppeteer browser arguments
154+
- `--browser-args <args>` - Additional Playwright browser arguments
142155
- `--headless` - Run browser in headless mode (default: true)
156+
- `--no-headless` - Run browser with a visible window
143157
- `--full-page` - Capture full page screenshots (default: true)
144158
- `--no-full-page` - Capture viewport-only screenshots
145159
- `--timeout <ms>` - Screenshot timeout in milliseconds (default: 45000)
146160
- `--dry-run` - Print discovered pages and task count without capturing screenshots
147161
- `--use-sitemap` - Use sitemap.xml for page discovery (default: true)
162+
- `--no-use-sitemap` - Disable sitemap.xml page discovery
148163
- `--sitemap-path <path>` - Path to sitemap.xml relative to build directory
149164

150165
## Page Discovery
@@ -234,16 +249,21 @@ Patterns support glob-like syntax:
234249

235250
## Screenshot Naming
236251

237-
Screenshots are named based on the page path, with viewport information stored as properties for better grouping:
252+
Screenshots are named based on the page path. The plugin records browser,
253+
viewport, viewport dimensions, page URL, and capture mode metadata
254+
automatically. You can add custom screenshot `properties` from config when you
255+
need extra signature dimensions such as theme, locale, or auth state:
238256

239257
**Name format:** `path-to-page` (slashes replaced with hyphens)
240258

241-
**Properties:** Viewport metadata (`viewport`, `viewportWidth`, `viewportHeight`)
259+
**Properties:** Browser, viewport, URL, capture-mode metadata, and any custom
260+
properties (`browser`, `viewport`, `viewport_width`, `viewport_height`, `url`,
261+
`fullPage`, plus user-defined fields)
242262

243263
Examples:
244-
- Name: `index`, Properties: `{ viewport: 'mobile', viewportWidth: 375, viewportHeight: 667 }`
245-
- Name: `blog-post-1`, Properties: `{ viewport: 'desktop', viewportWidth: 1920, viewportHeight: 1080 }`
246-
- Name: `docs-getting-started`, Properties: `{ viewport: 'tablet', viewportWidth: 768, viewportHeight: 1024 }`
264+
- Name: `index`, Properties: `{ browser: 'chromium', viewport: 'mobile', viewport_width: 375, viewport_height: 667, url: 'http://localhost:3000/' }`
265+
- Name: `blog-post-1`, Properties: `{ browser: 'chromium', viewport: 'desktop', viewport_width: 1920, viewport_height: 1080, url: 'http://localhost:3000/blog/post-1' }`
266+
- Name: `docs-getting-started`, Properties: `{ browser: 'webkit', viewport: 'tablet', viewport_width: 768, viewport_height: 1024, url: 'http://localhost:3000/docs/getting-started' }`
247267

248268
This approach allows Vizzly to group screenshots by viewport while keeping names clean and compatible with file system restrictions.
249269

clients/static-site/examples/sample-page.html

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<title>Sample Page - Vizzly Static Site Example</title>
77

8-
<!-- Vizzly meta tags for per-page configuration -->
9-
<!-- Only capture mobile and desktop viewports for this page -->
10-
<meta name="vizzly:viewports" content="mobile,desktop">
11-
12-
<!-- Reference named interaction from config -->
13-
<meta name="vizzly:interaction" content="scroll-to-footer">
14-
158
<style>
169
body {
1710
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
@@ -49,26 +42,22 @@
4942
<body>
5043
<header>
5144
<h1>Sample Static Site Page</h1>
52-
<p>Demonstrating Vizzly screenshot capture with meta tag configuration</p>
45+
<p>Demonstrating Vizzly screenshot capture with the companion config files</p>
5346
</header>
5447

5548
<main class="content">
5649
<section>
5750
<h2>About This Page</h2>
58-
<p>This page demonstrates how to configure Vizzly screenshot capture using HTML meta tags.</p>
51+
<p>This page is discovered like any other HTML file. Viewports and interactions are configured in the companion <code>vizzly.config.js</code> and <code>vizzly.static-site.js</code> files.</p>
5952

6053
<div class="card">
6154
<h3>Viewport Configuration</h3>
62-
<p>This page uses custom viewport configuration:</p>
63-
<code>&lt;meta name="vizzly:viewports" content="mobile,desktop"&gt;</code>
64-
<p>Only mobile and desktop viewports will be captured.</p>
55+
<p>The example config captures only mobile and desktop viewports for this page.</p>
6556
</div>
6657

6758
<div class="card">
6859
<h3>Interaction Hooks</h3>
69-
<p>This page references a named interaction hook:</p>
70-
<code>&lt;meta name="vizzly:interaction" content="scroll-to-footer"&gt;</code>
71-
<p>The page will scroll to the footer before the screenshot is taken.</p>
60+
<p>The example interactions file references a named hook that scrolls to the footer before the screenshot is taken.</p>
7261
</div>
7362
</section>
7463

clients/static-site/examples/vizzly.config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default {
1919

2020
comparison: {
2121
threshold: 0.1,
22+
minClusterSize: 2,
2223
},
2324

2425
// Static Site plugin configuration
@@ -48,8 +49,8 @@ export default {
4849
omitBackground: false, // Include page background
4950
},
5051

51-
// Parallel processing
52-
concurrency: 3, // Number of pages to process simultaneously
52+
// Parallel processing defaults to CPU-aware concurrency (min 2, max 8)
53+
// concurrency: 4,
5354

5455
// Page filtering
5556
include: null, // Optional: Only process pages matching this pattern
@@ -70,6 +71,6 @@ export default {
7071
// Storybook plugin configuration (if also using Storybook)
7172
storybook: {
7273
viewports: [{ name: 'default', width: 1920, height: 1080 }],
73-
concurrency: 3,
74+
// concurrency: 4,
7475
},
7576
};

clients/static-site/examples/vizzly.static-site.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ export default {
2222
let loadMoreBtn = await page.$('.load-more');
2323
if (loadMoreBtn) {
2424
await loadMoreBtn.click();
25-
// Wait for content to load
26-
await page.waitForTimeout(1000);
25+
await page.waitForSelector('.portfolio-item');
2726
}
2827
},
2928

clients/static-site/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,19 @@
2626
"type": "module",
2727
"exports": {
2828
".": {
29-
"import": "./dist/index.js",
30-
"require": "./dist/index.js"
29+
"import": "./dist/index.js"
30+
},
31+
"./plugin": {
32+
"import": "./dist/plugin.js"
3133
}
3234
},
3335
"main": "./dist/index.js",
3436
"vizzlyPlugin": "./dist/plugin.js",
3537
"files": [
3638
"dist",
3739
"README.md",
38-
"LICENSE",
39-
"CHANGELOG.md"
40+
"CHANGELOG.md",
41+
"LICENSE"
4042
],
4143
"scripts": {
4244
"build": "pnpm run clean && pnpm run compile",

clients/static-site/src/browser.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,10 @@ export async function launchBrowser(options = {}, dependencies = {}) {
8585
// Playwright throws plain Error objects without error codes, so we must match
8686
// on message patterns. These patterns cover known Playwright error messages:
8787
// - "Executable doesn't exist at <path>" (missing browser binary)
88-
// - "browserType.launch: ..." (launch failure context)
8988
// - "playwright install" (Playwright's own suggestion in the error)
9089
// - "download new browsers" (alternative phrasing in some versions)
9190
let isBrowserMissing =
9291
error.message.includes("Executable doesn't exist") ||
93-
error.message.includes('browserType.launch') ||
9492
error.message.includes('playwright install') ||
9593
error.message.includes('download new browsers');
9694

clients/static-site/src/config.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
* Reads from config.staticSite section of main vizzly.config.js
55
*/
66

7-
import { validateStaticSiteConfigWithDefaults } from './config-schema.js';
7+
import {
8+
getDefaultConcurrency,
9+
validateStaticSiteConfigWithDefaults,
10+
} from './config-schema.js';
811
import { loadInteractions } from './utils/interactions-loader.js';
912
import { parseViewport } from './utils/viewport.js';
1013

@@ -23,7 +26,7 @@ export let defaultConfig = {
2326
fullPage: true,
2427
omitBackground: false,
2528
},
26-
concurrency: 3,
29+
concurrency: getDefaultConcurrency(),
2730
include: null,
2831
exclude: null,
2932
pageDiscovery: {
@@ -50,6 +53,9 @@ export function parseCliOptions(options) {
5053
}
5154

5255
if (options.concurrency !== undefined) {
56+
if (!Number.isInteger(options.concurrency) || options.concurrency < 1) {
57+
throw new Error('concurrency must be a positive integer');
58+
}
5359
config.concurrency = options.concurrency;
5460
}
5561

@@ -82,6 +88,13 @@ export function parseCliOptions(options) {
8288
config.screenshot = { ...config.screenshot, timeout: options.timeout };
8389
}
8490

91+
if (options.requestTimeout !== undefined) {
92+
config.screenshot = {
93+
...config.screenshot,
94+
requestTimeout: options.requestTimeout,
95+
};
96+
}
97+
8598
if (options.useSitemap !== undefined) {
8699
config.pageDiscovery = {
87100
...config.pageDiscovery,
@@ -130,6 +143,10 @@ export function mergeConfigs(...configs) {
130143
...merged.interactions,
131144
...config.interactions,
132145
},
146+
pages: {
147+
...merged.pages,
148+
...config.pages,
149+
},
133150
viewports: config.viewports || merged.viewports,
134151
};
135152
}, {});
@@ -151,8 +168,7 @@ export function getPageConfig(globalConfig, page) {
151168
// Find matching page config by pattern
152169
let pageOverrides = null;
153170
for (let [pattern, config] of Object.entries(globalConfig.pages)) {
154-
// Simple pattern matching - exact match or wildcard
155-
if (pattern === page.path || matchPattern(pattern, page.path)) {
171+
if (matchesPagePattern(pattern, page.path)) {
156172
pageOverrides = config;
157173
break;
158174
}
@@ -202,6 +218,18 @@ function matchPattern(pattern, path) {
202218
return regex.test(path);
203219
}
204220

221+
function matchesPagePattern(pattern, path) {
222+
if (pattern === path) return true;
223+
if (!path) return false;
224+
225+
let value = String(path);
226+
let withSlash = value.startsWith('/') ? value : `/${value}`;
227+
let withoutSlash = withSlash === '/' ? '/' : withSlash.slice(1);
228+
let candidates = [...new Set([value, withSlash, withoutSlash])];
229+
230+
return candidates.some(candidate => matchPattern(pattern, candidate));
231+
}
232+
205233
/**
206234
* Load and merge all configuration sources
207235
* Priority: CLI options > vizzly.static-site.js > vizzlyConfig.staticSite > defaults

clients/static-site/src/crawler.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ export function generatePageUrl(baseUrl, page) {
250250
path = path.slice(0, -1);
251251
}
252252

253+
if (path.endsWith('.html')) {
254+
return `${baseUrl}${path}`;
255+
}
256+
253257
// Try /path.html first (most common convention)
254258
return `${baseUrl}${path}.html`;
255259
}

0 commit comments

Comments
 (0)