@qoretechnologies/qlip 0.1.3-pr.30.gc7b05dc → 0.1.3-pr.31.g4b62f97
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 +16 -1
- package/dist/config/parameters.d.ts +1 -0
- package/dist/config/parameters.d.ts.map +1 -1
- package/dist/config/parameters.js +3 -0
- package/dist/plugin/vitestPlugin.d.ts.map +1 -1
- package/dist/plugin/vitestPlugin.js +7 -3
- package/dist/runtime/fullPage.d.ts +5 -3
- package/dist/runtime/fullPage.d.ts.map +1 -1
- package/dist/runtime/fullPage.js +5 -3
- package/dist/runtime/screenshot.d.ts.map +1 -1
- package/dist/runtime/screenshot.js +8 -4
- package/dist/stories/Page.stories.d.ts +7 -0
- package/dist/stories/Page.stories.d.ts.map +1 -1
- package/dist/stories/Page.stories.js +11 -0
- package/dist/stories/TallPage.stories.d.ts +11 -1
- package/dist/stories/TallPage.stories.d.ts.map +1 -1
- package/dist/stories/TallPage.stories.js +16 -1
- package/dist/test-runner/index.d.ts.map +1 -1
- package/dist/test-runner/index.js +3 -0
- package/dist/types.d.ts +40 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/upload/autodetect.d.ts +23 -0
- package/dist/upload/autodetect.d.ts.map +1 -1
- package/dist/upload/autodetect.js +19 -0
- package/dist/upload/finalize.d.ts.map +1 -1
- package/dist/upload/finalize.js +12 -1
- package/dist/upload/upload.d.ts.map +1 -1
- package/dist/upload/upload.js +12 -0
- package/dist-cjs/config/parameters.js +3 -0
- package/dist-cjs/runtime/fullPage.js +5 -3
- package/dist-cjs/test-runner/index.js +3 -0
- package/dist-cjs/upload/autodetect.js +21 -1
- package/dist-cjs/upload/finalize.js +11 -0
- package/dist-cjs/upload/upload.js +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,6 +165,15 @@ qlip prints a one-time warning when it detects a shallow clone. It
|
|
|
165
165
|
never fails the upload over this — accuracy degrades, the run still
|
|
166
166
|
succeeds.
|
|
167
167
|
|
|
168
|
+
### Who triggered the build
|
|
169
|
+
|
|
170
|
+
On GitHub Actions qlip also sends the user who triggered the run
|
|
171
|
+
(`$GITHUB_ACTOR`, with `$GITHUB_ACTOR_ID` for a rename-proof avatar), so
|
|
172
|
+
the dashboard shows who pushed each build. Nothing to configure; set
|
|
173
|
+
`upload.triggeredByLogin` (and optionally `upload.triggeredById`) to
|
|
174
|
+
override it, for example from a CI system that isn't GitHub Actions.
|
|
175
|
+
Outside CI the field is simply omitted.
|
|
176
|
+
|
|
168
177
|
## Manual screenshots inside play
|
|
169
178
|
|
|
170
179
|
Use the existing **"Logged In"** story as a real-world example:
|
|
@@ -194,6 +203,7 @@ export const LoggedIn = {
|
|
|
194
203
|
qlip: {
|
|
195
204
|
skip: false,
|
|
196
205
|
viewport: { width: 1280, height: 720 },
|
|
206
|
+
fullSizeCaptures: false,
|
|
197
207
|
disableAnimations: true,
|
|
198
208
|
pauseAnimationsAtEnd: false,
|
|
199
209
|
fullPage: false,
|
|
@@ -219,6 +229,7 @@ Options precedence:
|
|
|
219
229
|
qlipVitestPlugin({
|
|
220
230
|
outputDir: './qlip/screenshots',
|
|
221
231
|
viewport: { width: 1280, height: 720 },
|
|
232
|
+
fullSizeCaptures: false,
|
|
222
233
|
disableAnimations: false,
|
|
223
234
|
pauseAnimationsAtEnd: false,
|
|
224
235
|
fullPage: false,
|
|
@@ -231,7 +242,11 @@ qlipVitestPlugin({
|
|
|
231
242
|
});
|
|
232
243
|
```
|
|
233
244
|
|
|
234
|
-
`fullPage` grows the viewport to the page's full content height for the capture and puts it back afterwards. A story that pins a phone viewport is otherwise photographed one screen tall; a browser "full page" screenshot would not help, because an app shell usually scrolls an inner box rather than the document. qlip measures the document and every scrolling element, grows the viewport by what they hide, and re-measures after each growth until nothing scrolls. Two kinds of box are not chased. A box that keeps its height when the viewport grows (a fixed-height code block, a capped list) stays scrolled, and the growth it asked for is given back, so the capture is not padded with blank space. Content that grows with the viewport (a tile sized to its column, a `vh` hero) would ask for more every round; it stops at the height its real content asked for. The height is capped at `fullPageMaxHeight` (default 10000); a page taller than that is captured at the cap with a `[qlip] fullPage:` warning naming the story. Under Vitest browser mode the test iframe is scaled down to fit the browser window, so
|
|
245
|
+
`fullPage` grows the viewport to the page's full content height for the capture and puts it back afterwards. A story that pins a phone viewport is otherwise photographed one screen tall; a browser "full page" screenshot would not help, because an app shell usually scrolls an inner box rather than the document. qlip measures the document and every scrolling element, grows the viewport by what they hide, and re-measures after each growth until nothing scrolls. Two kinds of box are not chased. A box that keeps its height when the viewport grows (a fixed-height code block, a capped list) stays scrolled, and the growth it asked for is given back, so the capture is not padded with blank space. Content that grows with the viewport (a tile sized to its column, a `vh` hero) would ask for more every round; it stops at the height its real content asked for. The height is capped at `fullPageMaxHeight` (default 10000); a page taller than that is captured at the cap with a `[qlip] fullPage:` warning naming the story. Under Vitest browser mode the test iframe is scaled down to fit the browser window, so a `fullPage` capture also grows the window — the Playwright context viewport — through a browser command the plugin registers (`qlipBrowserViewport`), and restores it first; the capture is 1:1, not a shrunk picture of a tall page. `fullSizeCaptures` does the same for every capture (below). The test-runner path grows its Playwright viewport the same way. The manifest keeps reporting the pinned `viewport` — that is the baseline key; the image is simply taller, and a capture whose content height changed shows on the server as a dimensions change rather than a pixel diff.
|
|
246
|
+
|
|
247
|
+
`fullSizeCaptures` captures every story at the full size of its `viewport`. Under Vitest browser mode the browser window stays at the Playwright context viewport — 1280×720 headless, unless you configure it — whatever the story's `viewport` says, and Vitest scales the test iframe down to fit the window. The layout is the story's, but the pixels are shrunk: a 1920×1080 story comes back 1280×720, a 390×844 phone story 333×720, with its text and any fine difference shrunk with it. With `fullSizeCaptures: true` the window grows to hold the viewport for each capture and is put back afterwards, so the PNG is exactly the viewport. The runner path always captures at full size, and `fullPage` always grows the window.
|
|
248
|
+
|
|
249
|
+
It is off by default, so upgrading qlip changes no baselines, and it will become the default in a future minor release. Turning it on changes the size of every capture whose viewport is larger than the window (stories that fit it are unaffected), and the server reports each of those as changed against its baseline, a dimensions change. Turn it on in one change on your default branch and accept those captures once.
|
|
235
250
|
|
|
236
251
|
`waitForIdleMs` waits for DOM mutations to settle before taking a screenshot. This is especially useful for animation libraries like `react-spring` that update inline styles via `requestAnimationFrame`, which bypasses CSS-based animation disabling. Increase it if you still catch mid-transition frames, or lower it for faster runs when your UI is static. `maxWaitForIdleMs` caps the wait so stories with continuously changing UI still complete.
|
|
237
252
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parameters.d.ts","sourceRoot":"","sources":["../../src/config/parameters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE5G,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;IACxB,oBAAoB,EAAE,gBAAgB,EAAE,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,yBAAyB,EAAE,MAAM,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,kBAAkB,GAAI,gCAIhC;IACD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,KAAG,
|
|
1
|
+
{"version":3,"file":"parameters.d.ts","sourceRoot":"","sources":["../../src/config/parameters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE5G,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;IACxB,oBAAoB,EAAE,gBAAgB,EAAE,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,yBAAyB,EAAE,MAAM,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,kBAAkB,GAAI,gCAIhC;IACD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,KAAG,mBAyDH,CAAC"}
|
|
@@ -2,6 +2,9 @@ export const resolveQlipOptions = ({ defaults, story, override, }) => {
|
|
|
2
2
|
return {
|
|
3
3
|
skip: override?.skip ?? story?.skip ?? defaults.skip,
|
|
4
4
|
viewport: override?.viewport ?? story?.viewport ?? defaults.viewport,
|
|
5
|
+
fullSizeCaptures: override?.fullSizeCaptures ??
|
|
6
|
+
story?.fullSizeCaptures ??
|
|
7
|
+
defaults.fullSizeCaptures,
|
|
5
8
|
disableAnimations: override?.disableAnimations ??
|
|
6
9
|
story?.disableAnimations ??
|
|
7
10
|
defaults.disableAnimations,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitestPlugin.d.ts","sourceRoot":"","sources":["../../src/plugin/vitestPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAU5C,OAAO,EAAE,iBAAiB,EAAqB,MAAM,aAAa,CAAC;AAanE,eAAO,MAAM,gBAAgB,GAAI,UAAS,iBAAsB,KAAG,
|
|
1
|
+
{"version":3,"file":"vitestPlugin.d.ts","sourceRoot":"","sources":["../../src/plugin/vitestPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAU5C,OAAO,EAAE,iBAAiB,EAAqB,MAAM,aAAa,CAAC;AAanE,eAAO,MAAM,gBAAgB,GAAI,UAAS,iBAAsB,KAAG,MAkRlE,CAAC"}
|
|
@@ -33,6 +33,9 @@ export const qlipVitestPlugin = (options = {}) => {
|
|
|
33
33
|
defaults: {
|
|
34
34
|
outputDir,
|
|
35
35
|
viewport: options.viewport ?? DEFAULT_VIEWPORT,
|
|
36
|
+
// Off by default — turning it on resizes every capture that was
|
|
37
|
+
// being scaled to fit the window. See QlipCaptureOptions.
|
|
38
|
+
fullSizeCaptures: options.fullSizeCaptures ?? false,
|
|
36
39
|
skip: false,
|
|
37
40
|
disableAnimations: options.disableAnimations ?? false,
|
|
38
41
|
pauseAnimationsAtEnd: options.pauseAnimationsAtEnd ?? false,
|
|
@@ -190,9 +193,10 @@ export const qlipVitestPlugin = (options = {}) => {
|
|
|
190
193
|
setupFiles: [setupFile],
|
|
191
194
|
globalSetup: [globalSetupFile],
|
|
192
195
|
reporters: reportersList,
|
|
193
|
-
// The runtime grows the browser window for a `fullPage` capture
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
+
// The runtime grows the browser window for a `fullPage` capture,
|
|
197
|
+
// and for every capture with `fullSizeCaptures`, through this
|
|
198
|
+
// command; it runs here, where the Playwright page is.
|
|
199
|
+
// `mergeConfig` merges it beside any commands the consumer
|
|
196
200
|
// declares.
|
|
197
201
|
browser: {
|
|
198
202
|
commands: {
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* viewport) stays as configured, and Vitest scales the iframe down to fit it.
|
|
11
11
|
* A 3,600px-tall iframe in a 1,440px window is photographed at 40% — the
|
|
12
12
|
* whole page, unreadable — and so is a plain 844px-tall phone story in a
|
|
13
|
-
* 720px window, at 85%. `createCaptureViewport` therefore grows the window
|
|
14
|
-
* to hold whatever viewport a capture asks for, pinned
|
|
15
|
-
* both back afterwards, the window first, so the iframe
|
|
13
|
+
* 720px window, at 85%. `createCaptureViewport` therefore grows the window,
|
|
14
|
+
* when it is given one, to hold whatever viewport a capture asks for, pinned
|
|
15
|
+
* or grown, and puts both back afterwards, the window first, so the iframe
|
|
16
|
+
* lands at scale 1. The runtime gives it the window for a `fullPage` capture
|
|
17
|
+
* and, with `fullSizeCaptures` on, for every capture.
|
|
16
18
|
*
|
|
17
19
|
* This module has no imports: the runtime, the test-runner and the unit
|
|
18
20
|
* project (which has no DOM and feeds it fakes) all share it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fullPage.d.ts","sourceRoot":"","sources":["../../src/runtime/fullPage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fullPage.d.ts","sourceRoot":"","sources":["../../src/runtime/fullPage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,oBAAoB,GAAI,UAAU;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE;QACJ,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,GAAG,kBAAkB,CAAC,CAAC;QACrE,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa,CAAC,CAAC;KACvD,CAAC;CACH,KAAG,MAsEH,CAAC;AAEF,gEAAgE;AAChE,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GAAU,mEAOzC;IACD,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,KAAG,OAAO,CAAC,cAAc,CAmBzB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,MAAM,EACf,QAAQ,cAAc,EACtB,WAAW,MAAM,KAChB,MAAM,GAAG,IAKF,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAAG,CACnC,IAAI,CAAC,EAAE,YAAY,GAAG,IAAI,KACvB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;AAElC;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAI,6CAInC;IACD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAC7C,KAAG;IACF,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CA4C9B,CAAC"}
|
package/dist/runtime/fullPage.js
CHANGED
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* viewport) stays as configured, and Vitest scales the iframe down to fit it.
|
|
11
11
|
* A 3,600px-tall iframe in a 1,440px window is photographed at 40% — the
|
|
12
12
|
* whole page, unreadable — and so is a plain 844px-tall phone story in a
|
|
13
|
-
* 720px window, at 85%. `createCaptureViewport` therefore grows the window
|
|
14
|
-
* to hold whatever viewport a capture asks for, pinned
|
|
15
|
-
* both back afterwards, the window first, so the iframe
|
|
13
|
+
* 720px window, at 85%. `createCaptureViewport` therefore grows the window,
|
|
14
|
+
* when it is given one, to hold whatever viewport a capture asks for, pinned
|
|
15
|
+
* or grown, and puts both back afterwards, the window first, so the iframe
|
|
16
|
+
* lands at scale 1. The runtime gives it the window for a `fullPage` capture
|
|
17
|
+
* and, with `fullSizeCaptures` on, for every capture.
|
|
16
18
|
*
|
|
17
19
|
* This module has no imports: the runtime, the test-runner and the unit
|
|
18
20
|
* project (which has no DOM and feeds it fakes) all share it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/runtime/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,qBAAqB,EACrB,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAqCrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AA8L1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM;AACf;;;;;;;GAOG;AACH,gBAAgB,MAAM,KACrB;IAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAiCvD,CAAC;AAwDF;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,GAAI,qEAIxC;IACD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;CAChC,SA+DA,CAAC;
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/runtime/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,qBAAqB,EACrB,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAqCrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AA8L1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM;AACf;;;;;;;GAOG;AACH,gBAAgB,MAAM,KACrB;IAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAiCvD,CAAC;AAwDF;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,GAAI,qEAIxC;IACD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;CAChC,SA+DA,CAAC;AAicF,eAAO,MAAM,UAAU,GACrB,KAAK,gBAAgB,EACrB,gBAAgB,MAAM,GAAG,qBAAqB,EAC9C,UAAU,qBAAqB,kBA0BhC,CAAC;AAEF,KAAK,eAAe,GAAG,WAAW,GAAG;IAAE,KAAK,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC;AA8ClE,eAAO,MAAM,qBAAqB,GAAU,KAAK,eAAe,kBAiG/D,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAU,KAAK,eAAe,kBAkEhE,CAAC"}
|
|
@@ -491,13 +491,17 @@ const captureScreenshot = async ({ kind, story, screenshotName, options, testErr
|
|
|
491
491
|
// throws.
|
|
492
492
|
let error = testError ?? null;
|
|
493
493
|
let cleanupMasks = null;
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
494
|
+
// Unless the browser window grows with it, a viewport larger than the
|
|
495
|
+
// window is captured scaled down to fit it (see ./fullPage.ts). Growing it
|
|
496
|
+
// resizes captures that already have baselines, so it is opt-in
|
|
497
|
+
// (`fullSizeCaptures`); a `fullPage` capture always grows it, since scaled
|
|
498
|
+
// it is unreadable.
|
|
497
499
|
const captureViewport = createCaptureViewport({
|
|
498
500
|
pinned: resolved.viewport,
|
|
499
501
|
iframe: (width, height) => page.viewport(width, height),
|
|
500
|
-
window:
|
|
502
|
+
window: resolved.fullSizeCaptures || resolved.fullPage
|
|
503
|
+
? commands.qlipBrowserViewport
|
|
504
|
+
: undefined,
|
|
501
505
|
});
|
|
502
506
|
try {
|
|
503
507
|
await captureViewport.set(resolved.viewport.width, resolved.viewport.height);
|
|
@@ -9,6 +9,13 @@ declare const meta: {
|
|
|
9
9
|
export default meta;
|
|
10
10
|
type Story = StoryObj<typeof meta>;
|
|
11
11
|
export declare const LoggedOut: Story;
|
|
12
|
+
/**
|
|
13
|
+
* A desktop viewport wider and taller than the 1280×720 headless browser
|
|
14
|
+
* window. `fullSizeCaptures` grows the window to hold it, so the capture is
|
|
15
|
+
* 1920×1080; without it Vitest would scale the story down to 1280×720. The
|
|
16
|
+
* e2e test reads the PNG.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DesktopFullSize: Story;
|
|
12
19
|
export declare const LoggedIn: Story;
|
|
13
20
|
export declare const ElementWithFullHeight: Story;
|
|
14
21
|
export declare const WithConsoleError: Story;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Page.stories.d.ts","sourceRoot":"","sources":["../../src/stories/Page.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAO5D,QAAA,MAAM,IAAI;;;;;;CAOmB,CAAC;AAE9B,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAGnC,eAAO,MAAM,SAAS,EAAE,KAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"Page.stories.d.ts","sourceRoot":"","sources":["../../src/stories/Page.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAO5D,QAAA,MAAM,IAAI;;;;;;CAOmB,CAAC;AAE9B,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAGnC,eAAO,MAAM,SAAS,EAAE,KAAU,CAAC;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,KAI7B,CAAC;AAGF,eAAO,MAAM,QAAQ,EAAE,KAatB,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,KAQnC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAS9B,CAAC"}
|
|
@@ -12,6 +12,17 @@ const meta = {
|
|
|
12
12
|
};
|
|
13
13
|
export default meta;
|
|
14
14
|
export const LoggedOut = {};
|
|
15
|
+
/**
|
|
16
|
+
* A desktop viewport wider and taller than the 1280×720 headless browser
|
|
17
|
+
* window. `fullSizeCaptures` grows the window to hold it, so the capture is
|
|
18
|
+
* 1920×1080; without it Vitest would scale the story down to 1280×720. The
|
|
19
|
+
* e2e test reads the PNG.
|
|
20
|
+
*/
|
|
21
|
+
export const DesktopFullSize = {
|
|
22
|
+
parameters: {
|
|
23
|
+
qlip: { viewport: { width: 1920, height: 1080 }, fullSizeCaptures: true },
|
|
24
|
+
},
|
|
25
|
+
};
|
|
15
26
|
// More on component testing: https://storybook.js.org/docs/writing-tests/interaction-testing
|
|
16
27
|
export const LoggedIn = {
|
|
17
28
|
play: async (ctx) => {
|
|
@@ -15,8 +15,18 @@ type Story = StoryObj<typeof meta>;
|
|
|
15
15
|
* The manifest keeps the pinned viewport as the baseline key.
|
|
16
16
|
*/
|
|
17
17
|
export declare const ScrollsAnInnerBox: Story;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* The same page without `fullPage`: one screen, the rest scrolled away. The
|
|
20
|
+
* 844px-tall viewport does not fit the 720px headless browser window, so
|
|
21
|
+
* Vitest scales the story down to fit it and the capture comes back 333×720
|
|
22
|
+
* — the size such a capture has always had. The e2e test reads it.
|
|
23
|
+
*/
|
|
19
24
|
export declare const OneScreen: Story;
|
|
25
|
+
/**
|
|
26
|
+
* `OneScreen` with `fullSizeCaptures`: qlip grows the browser window to hold
|
|
27
|
+
* the viewport, so the capture is exactly 390×844.
|
|
28
|
+
*/
|
|
29
|
+
export declare const OneScreenFullSize: Story;
|
|
20
30
|
/**
|
|
21
31
|
* `fullPage` on an ordinary page whose only scroller is a fixed-height box:
|
|
22
32
|
* more viewport never reveals it, so qlip gives the growth back and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TallPage.stories.d.ts","sourceRoot":"","sources":["../../src/stories/TallPage.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAK5D,QAAA,MAAM,IAAI;;;;;;CAMuB,CAAC;AAElC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAgBnC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAK/B,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"TallPage.stories.d.ts","sourceRoot":"","sources":["../../src/stories/TallPage.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAK5D,QAAA,MAAM,IAAI;;;;;;CAMuB,CAAC;AAElC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAgBnC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAK/B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,KAKvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAK/B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,KAU5B,CAAC"}
|
|
@@ -33,13 +33,28 @@ export const ScrollsAnInnerBox = {
|
|
|
33
33
|
},
|
|
34
34
|
play: expectsAnInnerScroller,
|
|
35
35
|
};
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The same page without `fullPage`: one screen, the rest scrolled away. The
|
|
38
|
+
* 844px-tall viewport does not fit the 720px headless browser window, so
|
|
39
|
+
* Vitest scales the story down to fit it and the capture comes back 333×720
|
|
40
|
+
* — the size such a capture has always had. The e2e test reads it.
|
|
41
|
+
*/
|
|
37
42
|
export const OneScreen = {
|
|
38
43
|
parameters: {
|
|
39
44
|
qlip: { viewport: PHONE },
|
|
40
45
|
},
|
|
41
46
|
play: expectsAnInnerScroller,
|
|
42
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* `OneScreen` with `fullSizeCaptures`: qlip grows the browser window to hold
|
|
50
|
+
* the viewport, so the capture is exactly 390×844.
|
|
51
|
+
*/
|
|
52
|
+
export const OneScreenFullSize = {
|
|
53
|
+
parameters: {
|
|
54
|
+
qlip: { viewport: PHONE, fullSizeCaptures: true },
|
|
55
|
+
},
|
|
56
|
+
play: expectsAnInnerScroller,
|
|
57
|
+
};
|
|
43
58
|
/**
|
|
44
59
|
* `fullPage` on an ordinary page whose only scroller is a fixed-height box:
|
|
45
60
|
* more viewport never reveals it, so qlip gives the growth back and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test-runner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAkBH,OAAO,KAAK,EACV,kBAAkB,EAGlB,iBAAiB,EACjB,cAAc,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,YAAY,EAIb,MAAM,qBAAqB,CAAC;AAS7B;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACnE,UAAU,CAAC,IAAI,EAAE;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACtE;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,gBAAgB,KACtB,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,cAAc,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAC1D;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test-runner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAkBH,OAAO,KAAK,EACV,kBAAkB,EAGlB,iBAAiB,EACjB,cAAc,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,YAAY,EAIb,MAAM,qBAAqB,CAAC;AAS7B;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACnE,UAAU,CAAC,IAAI,EAAE;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACtE;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,gBAAgB,KACtB,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,cAAc,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAC1D;AAkID;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM,mBAAmB,EACzB,SAAS,gBAAgB,EACzB,UAAS,mBAAwB,KAChC,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAelC,CAAC;AA0JF,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -60,6 +60,9 @@ const buildResolvedDefaults = (override) => {
|
|
|
60
60
|
return {
|
|
61
61
|
outputDir: DEFAULT_OUTPUT_DIR,
|
|
62
62
|
viewport: fallbackViewport,
|
|
63
|
+
// Not read on this path: `page.setViewportSize` sizes the page itself,
|
|
64
|
+
// so every capture here is full size already.
|
|
65
|
+
fullSizeCaptures: override?.fullSizeCaptures ?? false,
|
|
63
66
|
skip: false,
|
|
64
67
|
disableAnimations: override?.disableAnimations ?? false,
|
|
65
68
|
pauseAnimationsAtEnd: override?.pauseAnimationsAtEnd ?? false,
|
package/dist/types.d.ts
CHANGED
|
@@ -6,12 +6,29 @@ export interface QlipCaptureOptions {
|
|
|
6
6
|
skip?: boolean;
|
|
7
7
|
/**
|
|
8
8
|
* The capture's viewport, and the baseline key on the server. Under
|
|
9
|
-
* Vitest browser mode a viewport the browser window
|
|
10
|
-
*
|
|
11
|
-
* scales the test iframe down to fit and the capture comes back smaller
|
|
12
|
-
* than the viewport it names.
|
|
9
|
+
* Vitest browser mode a viewport larger than the browser window is
|
|
10
|
+
* captured scaled down to fit it, unless `fullSizeCaptures` is on.
|
|
13
11
|
*/
|
|
14
12
|
viewport?: QlipViewport;
|
|
13
|
+
/**
|
|
14
|
+
* Capture at the full size of `viewport`, pixel for pixel. Under Vitest
|
|
15
|
+
* browser mode the browser window (the Playwright context viewport,
|
|
16
|
+
* 1280×720 headless unless configured) does not follow `viewport`: Vitest
|
|
17
|
+
* sizes the test iframe to the viewport and scales the iframe down to fit
|
|
18
|
+
* the window. The layout is the viewport's, but the pixels are shrunk: a
|
|
19
|
+
* 1920×1080 story is captured at 1280×720, a 390×844 phone story at
|
|
20
|
+
* 333×720. With this on, the window grows to hold the viewport for the
|
|
21
|
+
* capture and is put back afterwards, so the capture is exactly `viewport`.
|
|
22
|
+
*
|
|
23
|
+
* Off by default: turning it on changes the size of every capture that was
|
|
24
|
+
* being scaled, and the server reports each one as changed against its
|
|
25
|
+
* baseline, so accept them once. A `fullPage` capture grows the window
|
|
26
|
+
* either way. The Storybook test-runner path sizes the page's own viewport
|
|
27
|
+
* and always captures at full size.
|
|
28
|
+
*
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
fullSizeCaptures?: boolean;
|
|
15
32
|
disableAnimations?: boolean;
|
|
16
33
|
pauseAnimationsAtEnd?: boolean;
|
|
17
34
|
/**
|
|
@@ -38,12 +55,12 @@ export interface QlipCaptureOptions {
|
|
|
38
55
|
* scrolled and gives its growth back, and content that grows with the
|
|
39
56
|
* viewport is not chased. Capped at `fullPageMaxHeight`. Under Vitest
|
|
40
57
|
* browser mode the browser window (the Playwright context viewport) grows
|
|
41
|
-
* with the test iframe,
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
58
|
+
* with the test iframe whatever `fullSizeCaptures` says, or the capture
|
|
59
|
+
* would be scaled down to fit the window; both are put back afterwards.
|
|
60
|
+
* The manifest still reports the pinned `viewport` — that is the baseline
|
|
61
|
+
* key; the picture is simply taller. A capture whose content height
|
|
62
|
+
* changed shows on the server as a dimensions change rather than a pixel
|
|
63
|
+
* diff.
|
|
47
64
|
*
|
|
48
65
|
* @default false
|
|
49
66
|
*/
|
|
@@ -132,6 +149,19 @@ export interface QlipUploadOptions {
|
|
|
132
149
|
* "View PR" link. See `design/UPLOAD.md`.
|
|
133
150
|
*/
|
|
134
151
|
pullRequestUrl?: string;
|
|
152
|
+
/**
|
|
153
|
+
* GitHub login of the user who triggered the build — shown as the
|
|
154
|
+
* build's author in the dashboard. Auto-detected from `$GITHUB_ACTOR`
|
|
155
|
+
* when omitted. Undefined (not sent) outside GitHub Actions; a
|
|
156
|
+
* dashboard upload is attributed from the signed-in session instead.
|
|
157
|
+
*/
|
|
158
|
+
triggeredByLogin?: string;
|
|
159
|
+
/**
|
|
160
|
+
* GitHub account id of that user (`$GITHUB_ACTOR_ID`). Stable across
|
|
161
|
+
* a login rename, so the server prefers it for the avatar. Only sent
|
|
162
|
+
* alongside `triggeredByLogin`.
|
|
163
|
+
*/
|
|
164
|
+
triggeredById?: number;
|
|
135
165
|
/** Skip upload entirely (handy for local-only runs). */
|
|
136
166
|
disabled?: boolean;
|
|
137
167
|
/**
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,6 +38,29 @@ export declare const autodetectBaseBranch: () => string | undefined;
|
|
|
38
38
|
* `qlip-server/design/UPLOAD.md`.
|
|
39
39
|
*/
|
|
40
40
|
export declare const autodetectPullRequestUrl: () => string | undefined;
|
|
41
|
+
/** The GitHub user whose action produced the build. */
|
|
42
|
+
export interface IUploadTriggeredBy {
|
|
43
|
+
/** GitHub login (`$GITHUB_ACTOR`). */
|
|
44
|
+
login: string;
|
|
45
|
+
/**
|
|
46
|
+
* GitHub account id (`$GITHUB_ACTOR_ID`). Stable across a rename, so
|
|
47
|
+
* the server prefers it for the avatar; omitted on an older runner
|
|
48
|
+
* that doesn't set it.
|
|
49
|
+
*/
|
|
50
|
+
id?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Who triggered the CI run — the user the dashboard shows as the
|
|
54
|
+
* build's author. GitHub Actions sets `$GITHUB_ACTOR` (the login) and
|
|
55
|
+
* `$GITHUB_ACTOR_ID` (the numeric account id) on every run.
|
|
56
|
+
*
|
|
57
|
+
* Env-only by design: outside CI there is no GitHub identity to read
|
|
58
|
+
* (a git author is an email, not a login), and a dashboard upload is
|
|
59
|
+
* attributed server-side from the signed-in session. Returns undefined
|
|
60
|
+
* for local runs; the server stores it as nullable, so absence just
|
|
61
|
+
* means "author unknown". See `qlip-server/design/UPLOAD.md`.
|
|
62
|
+
*/
|
|
63
|
+
export declare const autodetectTriggeredBy: () => IUploadTriggeredBy | undefined;
|
|
41
64
|
/**
|
|
42
65
|
* Commit SHAs from HEAD backward, nearest-first, capped at
|
|
43
66
|
* MAX_ANCESTOR_COMMITS. The server walks this list to find the nearest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autodetect.d.ts","sourceRoot":"","sources":["../../src/upload/autodetect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAqBH,eAAO,MAAM,gBAAgB,QAAO,MAAM,GAAG,SAW5C,CAAC;AAkCF,eAAO,MAAM,gBAAgB,QAAO,MAAM,GAAG,SAS5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAAO,MAAM,GAAG,SAGhD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAAO,MAAM,GAAG,SAcpD,CAAC;AA0BF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,QAAO,MAAM,EAAE,GAAG,SAgBvD,CAAC"}
|
|
1
|
+
{"version":3,"file":"autodetect.d.ts","sourceRoot":"","sources":["../../src/upload/autodetect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAqBH,eAAO,MAAM,gBAAgB,QAAO,MAAM,GAAG,SAW5C,CAAC;AAkCF,eAAO,MAAM,gBAAgB,QAAO,MAAM,GAAG,SAS5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,QAAO,MAAM,GAAG,SAGhD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAAO,MAAM,GAAG,SAcpD,CAAC;AAEF,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,QAAO,kBAAkB,GAAG,SAM7D,CAAC;AA0BF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,QAAO,MAAM,EAAE,GAAG,SAgBvD,CAAC"}
|
|
@@ -127,6 +127,25 @@ export const autodetectPullRequestUrl = () => {
|
|
|
127
127
|
const base = serverUrl.replace(/\/+$/, '');
|
|
128
128
|
return `${base}/${repo}/pull/${prNumber}`;
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* Who triggered the CI run — the user the dashboard shows as the
|
|
132
|
+
* build's author. GitHub Actions sets `$GITHUB_ACTOR` (the login) and
|
|
133
|
+
* `$GITHUB_ACTOR_ID` (the numeric account id) on every run.
|
|
134
|
+
*
|
|
135
|
+
* Env-only by design: outside CI there is no GitHub identity to read
|
|
136
|
+
* (a git author is an email, not a login), and a dashboard upload is
|
|
137
|
+
* attributed server-side from the signed-in session. Returns undefined
|
|
138
|
+
* for local runs; the server stores it as nullable, so absence just
|
|
139
|
+
* means "author unknown". See `qlip-server/design/UPLOAD.md`.
|
|
140
|
+
*/
|
|
141
|
+
export const autodetectTriggeredBy = () => {
|
|
142
|
+
const login = process.env['GITHUB_ACTOR']?.trim();
|
|
143
|
+
if (!login || login.length === 0)
|
|
144
|
+
return undefined;
|
|
145
|
+
const rawId = process.env['GITHUB_ACTOR_ID']?.trim();
|
|
146
|
+
const id = rawId && /^\d+$/.test(rawId) ? Number(rawId) : undefined;
|
|
147
|
+
return id !== undefined ? { login, id } : { login };
|
|
148
|
+
};
|
|
130
149
|
let shallowWarningEmitted = false;
|
|
131
150
|
/**
|
|
132
151
|
* A shallow clone (CI default for `actions/checkout`) only has the tip
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/upload/finalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/upload/finalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAqBxE;;;;GAIG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AA6BD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,eAAO,MAAM,aAAa,GACxB,MAAM,qBAAqB,KAC1B,OAAO,CAAC,IAAI,CA2Hd,CAAC"}
|
package/dist/upload/finalize.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* dedupes — if both paths fire in the same run, only the first
|
|
18
18
|
* actually does the work.
|
|
19
19
|
*/
|
|
20
|
-
import { autodetectAncestorCommits, autodetectBaseBranch, autodetectBranch, autodetectCommit, autodetectPullRequestUrl, } from './autodetect.js';
|
|
20
|
+
import { autodetectAncestorCommits, autodetectBaseBranch, autodetectBranch, autodetectCommit, autodetectPullRequestUrl, autodetectTriggeredBy, } from './autodetect.js';
|
|
21
21
|
import { buildCaptureReport, describeBaselineCollisions, describeCollisions, describeMissingCaptures, describePartialBuild, writeCaptureReport, } from './capture-report.js';
|
|
22
22
|
import { mergeManifestFragments } from './manifest.js';
|
|
23
23
|
import { QlipUploadError, uploadBuild, resolveServerUrl } from './upload.js';
|
|
@@ -118,6 +118,15 @@ export const finalizeBuild = async (opts) => {
|
|
|
118
118
|
const baseBranch = opts.upload.baseBranch ?? autodetectBaseBranch();
|
|
119
119
|
const ancestorCommits = opts.upload.ancestorCommits ?? autodetectAncestorCommits();
|
|
120
120
|
const pullRequestUrl = opts.upload.pullRequestUrl ?? autodetectPullRequestUrl();
|
|
121
|
+
// An explicit login wins outright (with whatever id came with it);
|
|
122
|
+
// otherwise both come from the CI env together.
|
|
123
|
+
const detectedTriggeredBy = opts.upload.triggeredByLogin === undefined
|
|
124
|
+
? autodetectTriggeredBy()
|
|
125
|
+
: undefined;
|
|
126
|
+
const triggeredByLogin = opts.upload.triggeredByLogin ?? detectedTriggeredBy?.login;
|
|
127
|
+
const triggeredById = opts.upload.triggeredByLogin !== undefined
|
|
128
|
+
? opts.upload.triggeredById
|
|
129
|
+
: detectedTriggeredBy?.id;
|
|
121
130
|
const resolved = {
|
|
122
131
|
...opts.upload,
|
|
123
132
|
...(branch !== undefined ? { branch } : {}),
|
|
@@ -125,6 +134,8 @@ export const finalizeBuild = async (opts) => {
|
|
|
125
134
|
...(baseBranch !== undefined ? { baseBranch } : {}),
|
|
126
135
|
...(ancestorCommits !== undefined ? { ancestorCommits } : {}),
|
|
127
136
|
...(pullRequestUrl !== undefined ? { pullRequestUrl } : {}),
|
|
137
|
+
...(triggeredByLogin !== undefined ? { triggeredByLogin } : {}),
|
|
138
|
+
...(triggeredById !== undefined ? { triggeredById } : {}),
|
|
128
139
|
};
|
|
129
140
|
try {
|
|
130
141
|
const result = await uploadBuild({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/upload/upload.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGnE,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,QAAQ,EAAE,IAAI,GAAG,QAAQ,CAAC;IAC1B,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,iBAAiB,CAAC;IAC3B,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,sCAAsC,CAAC;AAOtE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,KAC5C,MAAsE,CAAC;AAE1E,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;gBAG3B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,YAAuB;CAUjC;
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/upload/upload.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGnE,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,QAAQ,EAAE,IAAI,GAAG,QAAQ,CAAC;IAC1B,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,iBAAiB,CAAC;IAC3B,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,sCAAsC,CAAC;AAOtE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,KAC5C,MAAsE,CAAC;AAE1E,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;gBAG3B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,YAAuB;CAUjC;AA6JD,eAAO,MAAM,WAAW,GACtB,OAAO,eAAe,KACrB,OAAO,CAAC,gBAAgB,CAkG1B,CAAC"}
|
package/dist/upload/upload.js
CHANGED
|
@@ -125,6 +125,12 @@ const uploadBuildLegacy = async (fetchImpl, baseUrl, buildDir, options, manifest
|
|
|
125
125
|
if (options.pullRequestUrl !== undefined) {
|
|
126
126
|
form.append('pullRequestUrl', options.pullRequestUrl);
|
|
127
127
|
}
|
|
128
|
+
if (options.triggeredByLogin !== undefined) {
|
|
129
|
+
form.append('triggeredByLogin', options.triggeredByLogin);
|
|
130
|
+
}
|
|
131
|
+
if (options.triggeredById !== undefined) {
|
|
132
|
+
form.append('triggeredById', String(options.triggeredById));
|
|
133
|
+
}
|
|
128
134
|
const response = await fetchImpl(`${baseUrl}/api/builds/upload`, {
|
|
129
135
|
method: 'POST',
|
|
130
136
|
body: form,
|
|
@@ -165,6 +171,12 @@ export const uploadBuild = async (input) => {
|
|
|
165
171
|
...(options.pullRequestUrl !== undefined
|
|
166
172
|
? { pullRequestUrl: options.pullRequestUrl }
|
|
167
173
|
: {}),
|
|
174
|
+
...(options.triggeredByLogin !== undefined
|
|
175
|
+
? { triggeredByLogin: options.triggeredByLogin }
|
|
176
|
+
: {}),
|
|
177
|
+
...(options.triggeredById !== undefined
|
|
178
|
+
? { triggeredById: options.triggeredById }
|
|
179
|
+
: {}),
|
|
168
180
|
}),
|
|
169
181
|
});
|
|
170
182
|
// 404 ⇒ server predates the v2 routes; fall back to legacy multipart.
|
|
@@ -5,6 +5,9 @@ const resolveQlipOptions = ({ defaults, story, override, }) => {
|
|
|
5
5
|
return {
|
|
6
6
|
skip: override?.skip ?? story?.skip ?? defaults.skip,
|
|
7
7
|
viewport: override?.viewport ?? story?.viewport ?? defaults.viewport,
|
|
8
|
+
fullSizeCaptures: override?.fullSizeCaptures ??
|
|
9
|
+
story?.fullSizeCaptures ??
|
|
10
|
+
defaults.fullSizeCaptures,
|
|
8
11
|
disableAnimations: override?.disableAnimations ??
|
|
9
12
|
story?.disableAnimations ??
|
|
10
13
|
defaults.disableAnimations,
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
* viewport) stays as configured, and Vitest scales the iframe down to fit it.
|
|
12
12
|
* A 3,600px-tall iframe in a 1,440px window is photographed at 40% — the
|
|
13
13
|
* whole page, unreadable — and so is a plain 844px-tall phone story in a
|
|
14
|
-
* 720px window, at 85%. `createCaptureViewport` therefore grows the window
|
|
15
|
-
* to hold whatever viewport a capture asks for, pinned
|
|
16
|
-
* both back afterwards, the window first, so the iframe
|
|
14
|
+
* 720px window, at 85%. `createCaptureViewport` therefore grows the window,
|
|
15
|
+
* when it is given one, to hold whatever viewport a capture asks for, pinned
|
|
16
|
+
* or grown, and puts both back afterwards, the window first, so the iframe
|
|
17
|
+
* lands at scale 1. The runtime gives it the window for a `fullPage` capture
|
|
18
|
+
* and, with `fullSizeCaptures` on, for every capture.
|
|
17
19
|
*
|
|
18
20
|
* This module has no imports: the runtime, the test-runner and the unit
|
|
19
21
|
* project (which has no DOM and feeds it fakes) all share it.
|
|
@@ -67,6 +67,9 @@ const buildResolvedDefaults = (override) => {
|
|
|
67
67
|
return {
|
|
68
68
|
outputDir: output_js_1.DEFAULT_OUTPUT_DIR,
|
|
69
69
|
viewport: fallbackViewport,
|
|
70
|
+
// Not read on this path: `page.setViewportSize` sizes the page itself,
|
|
71
|
+
// so every capture here is full size already.
|
|
72
|
+
fullSizeCaptures: override?.fullSizeCaptures ?? false,
|
|
70
73
|
skip: false,
|
|
71
74
|
disableAnimations: override?.disableAnimations ?? false,
|
|
72
75
|
pauseAnimationsAtEnd: override?.pauseAnimationsAtEnd ?? false,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `qlip-server/design/UPLOAD.md`.
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.autodetectAncestorCommits = exports.autodetectPullRequestUrl = exports.autodetectBaseBranch = exports.autodetectCommit = exports.autodetectBranch = void 0;
|
|
18
|
+
exports.autodetectAncestorCommits = exports.autodetectTriggeredBy = exports.autodetectPullRequestUrl = exports.autodetectBaseBranch = exports.autodetectCommit = exports.autodetectBranch = void 0;
|
|
19
19
|
const node_child_process_1 = require("node:child_process");
|
|
20
20
|
const node_fs_1 = require("node:fs");
|
|
21
21
|
/** Server-side cap on the ancestry list (`qlip-server/design/UPLOAD.md`). */
|
|
@@ -134,6 +134,26 @@ const autodetectPullRequestUrl = () => {
|
|
|
134
134
|
return `${base}/${repo}/pull/${prNumber}`;
|
|
135
135
|
};
|
|
136
136
|
exports.autodetectPullRequestUrl = autodetectPullRequestUrl;
|
|
137
|
+
/**
|
|
138
|
+
* Who triggered the CI run — the user the dashboard shows as the
|
|
139
|
+
* build's author. GitHub Actions sets `$GITHUB_ACTOR` (the login) and
|
|
140
|
+
* `$GITHUB_ACTOR_ID` (the numeric account id) on every run.
|
|
141
|
+
*
|
|
142
|
+
* Env-only by design: outside CI there is no GitHub identity to read
|
|
143
|
+
* (a git author is an email, not a login), and a dashboard upload is
|
|
144
|
+
* attributed server-side from the signed-in session. Returns undefined
|
|
145
|
+
* for local runs; the server stores it as nullable, so absence just
|
|
146
|
+
* means "author unknown". See `qlip-server/design/UPLOAD.md`.
|
|
147
|
+
*/
|
|
148
|
+
const autodetectTriggeredBy = () => {
|
|
149
|
+
const login = process.env['GITHUB_ACTOR']?.trim();
|
|
150
|
+
if (!login || login.length === 0)
|
|
151
|
+
return undefined;
|
|
152
|
+
const rawId = process.env['GITHUB_ACTOR_ID']?.trim();
|
|
153
|
+
const id = rawId && /^\d+$/.test(rawId) ? Number(rawId) : undefined;
|
|
154
|
+
return id !== undefined ? { login, id } : { login };
|
|
155
|
+
};
|
|
156
|
+
exports.autodetectTriggeredBy = autodetectTriggeredBy;
|
|
137
157
|
let shallowWarningEmitted = false;
|
|
138
158
|
/**
|
|
139
159
|
* A shallow clone (CI default for `actions/checkout`) only has the tip
|
|
@@ -122,6 +122,15 @@ const finalizeBuild = async (opts) => {
|
|
|
122
122
|
const baseBranch = opts.upload.baseBranch ?? (0, autodetect_js_1.autodetectBaseBranch)();
|
|
123
123
|
const ancestorCommits = opts.upload.ancestorCommits ?? (0, autodetect_js_1.autodetectAncestorCommits)();
|
|
124
124
|
const pullRequestUrl = opts.upload.pullRequestUrl ?? (0, autodetect_js_1.autodetectPullRequestUrl)();
|
|
125
|
+
// An explicit login wins outright (with whatever id came with it);
|
|
126
|
+
// otherwise both come from the CI env together.
|
|
127
|
+
const detectedTriggeredBy = opts.upload.triggeredByLogin === undefined
|
|
128
|
+
? (0, autodetect_js_1.autodetectTriggeredBy)()
|
|
129
|
+
: undefined;
|
|
130
|
+
const triggeredByLogin = opts.upload.triggeredByLogin ?? detectedTriggeredBy?.login;
|
|
131
|
+
const triggeredById = opts.upload.triggeredByLogin !== undefined
|
|
132
|
+
? opts.upload.triggeredById
|
|
133
|
+
: detectedTriggeredBy?.id;
|
|
125
134
|
const resolved = {
|
|
126
135
|
...opts.upload,
|
|
127
136
|
...(branch !== undefined ? { branch } : {}),
|
|
@@ -129,6 +138,8 @@ const finalizeBuild = async (opts) => {
|
|
|
129
138
|
...(baseBranch !== undefined ? { baseBranch } : {}),
|
|
130
139
|
...(ancestorCommits !== undefined ? { ancestorCommits } : {}),
|
|
131
140
|
...(pullRequestUrl !== undefined ? { pullRequestUrl } : {}),
|
|
141
|
+
...(triggeredByLogin !== undefined ? { triggeredByLogin } : {}),
|
|
142
|
+
...(triggeredById !== undefined ? { triggeredById } : {}),
|
|
132
143
|
};
|
|
133
144
|
try {
|
|
134
145
|
const result = await (0, upload_js_1.uploadBuild)({
|
|
@@ -133,6 +133,12 @@ const uploadBuildLegacy = async (fetchImpl, baseUrl, buildDir, options, manifest
|
|
|
133
133
|
if (options.pullRequestUrl !== undefined) {
|
|
134
134
|
form.append('pullRequestUrl', options.pullRequestUrl);
|
|
135
135
|
}
|
|
136
|
+
if (options.triggeredByLogin !== undefined) {
|
|
137
|
+
form.append('triggeredByLogin', options.triggeredByLogin);
|
|
138
|
+
}
|
|
139
|
+
if (options.triggeredById !== undefined) {
|
|
140
|
+
form.append('triggeredById', String(options.triggeredById));
|
|
141
|
+
}
|
|
136
142
|
const response = await fetchImpl(`${baseUrl}/api/builds/upload`, {
|
|
137
143
|
method: 'POST',
|
|
138
144
|
body: form,
|
|
@@ -173,6 +179,12 @@ const uploadBuild = async (input) => {
|
|
|
173
179
|
...(options.pullRequestUrl !== undefined
|
|
174
180
|
? { pullRequestUrl: options.pullRequestUrl }
|
|
175
181
|
: {}),
|
|
182
|
+
...(options.triggeredByLogin !== undefined
|
|
183
|
+
? { triggeredByLogin: options.triggeredByLogin }
|
|
184
|
+
: {}),
|
|
185
|
+
...(options.triggeredById !== undefined
|
|
186
|
+
? { triggeredById: options.triggeredById }
|
|
187
|
+
: {}),
|
|
176
188
|
}),
|
|
177
189
|
});
|
|
178
190
|
// 404 ⇒ server predates the v2 routes; fall back to legacy multipart.
|